commit 69aa91d4033c63a63d152d8f57eae55f1c9a2857 Author: SlaveOfMagicbot Date: Sun Sep 27 12:35:14 2026 -0500 chore: baseline subdate2 snapshot for AI refactoring diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9154f4c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# ---> Java +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f23b14e5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ + • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . + ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· + ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ + ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ + ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ + Magicbane Emulator Project © 2013 - 2022 + www.magicbane.com + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 00000000..3468187d --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +![Logo](https://magicbane.com/images/MBLogo.jpg) + +# Magicbane Open-Source MMO Project + +### *The Community written Shadowbane emulator* + +[Magicbane](http://www.magicbane.com) +
+[Public Repository](http://repo.magicbane.com) +
+ + +> Magicbane is an emulator for the long dead but much beloved Ubisoft +> MMO, [Shadowbane](https://en.wikipedia.org/wiki/Shadowbane). +> The project was founded in 2013 with the concept of free availability of gameplay; players unencumbered by any factor +> other than a desire to again play a game they once loved. A game where the developers do not play is guaranteed to be +> the fairest game. + +The Magicbane Team has wanted to open source Shadowbane for half a decade. We are excited to now finally have the +opportunity, along with some new technology, to truly democratize Shadowbane. + +- Written in some 80k lines of Java 8 and bash. +- Project with real infrastructure; Production and development servers supporting multiple containerized apps. +- MagicBox container technology enables _instant deployment_ and trouble free development on modest hardware. +- MagicBox plugin capability allows for new and interesting player experiences. + +## Contributing + +- Project is released under an MIT license. Please see the LICENSE file. +- Inbound == Outbound. +- IntelliJ is the supported IDE. +- Write code today see it running on the Magicbane production server tomorrow. + +## Support + +Documentation is available through the Magicbane [Wiki](http://repo.magicbane.com/MagicBane/Server/wiki) +and [Discord server](www.magicbane.com). \ No newline at end of file diff --git a/src/discord/ChatChannel.java b/src/discord/ChatChannel.java new file mode 100644 index 00000000..a67e31fe --- /dev/null +++ b/src/discord/ChatChannel.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord; + +import engine.gameManager.ConfigManager; +import net.dv8tion.jda.api.entities.TextChannel; + +public enum ChatChannel { + + ANNOUNCE("MB_MAGICBOT_ANNOUNCE"), + SEPTIC("MB_MAGICBOT_SEPTIC"), + CHANGELOG("MB_MAGICBOT_CHANGELOG"), + POLITICAL("MB_MAGICBOT_POLITICAL"), + GENERAL("MB_MAGICBOT_GENERAL"), + FORTOFIX("MB_MAGICBOT_FORTOFIX"), + RECRUIT("MB_MAGICBOT_RECRUIT"), + MAGICBOX("MB_MAGICBOT_MAGICBOX"), + ADMINLOG("MB_MAGICBOT_ADMINLOG"); + + public final String configName; + public long channelID; + public TextChannel textChannel; + + ChatChannel(String configName) { + this.configName = configName; + } + + // Create text channel objects we will use + + public static void Init() { + + for (ChatChannel chatChannel : ChatChannel.values()) { + chatChannel.channelID = Long.parseLong(ConfigManager.valueOf(chatChannel.configName).getValue()); + chatChannel.textChannel = MagicBot.jda.getTextChannelById(chatChannel.channelID); + } + } +} diff --git a/src/discord/Database.java b/src/discord/Database.java new file mode 100644 index 00000000..c5175cab --- /dev/null +++ b/src/discord/Database.java @@ -0,0 +1,393 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package discord; + +import engine.Enum; +import engine.gameManager.ConfigManager; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class Database { + + public static Boolean online; + + static { + try { + Class.forName("com.mysql.cj.jdbc.Driver").newInstance(); + } catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) { + // TODO Auto-generated catch block + Logger.error(e.toString()); + online = false; + } + } + + // Load and instance the JDBC Driver + + public String sqlURI; + + public void configureDatabase() { + + // Build connection string from JSON object. + + sqlURI = "jdbc:mysql://"; + sqlURI += ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':' + ConfigManager.MB_DATABASE_PORT.getValue(); + sqlURI += '/' + ConfigManager.MB_DATABASE_NAME.getValue() + '?'; + sqlURI += "useServerPrepStmts=true"; + sqlURI += "&cachePrepStmts=false"; + sqlURI += "&cacheCallableStmts=true"; + sqlURI += "&characterEncoding=utf8"; + + online = true; + } + + public boolean updateAccountPassword(String discordAccountID, String newPassword) { + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement updateStatement = connection.prepareStatement("call discordUpdatePassword(?, ?)")) { + + updateStatement.setString(1, discordAccountID); + updateStatement.setString(2, newPassword); + + updateStatement.executeUpdate(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + return false; + } + } + + public boolean updateAccountStatus(String discordAccountID, Enum.AccountStatus accountStatus) { + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement updateStatement = connection.prepareStatement("update obj_account set `status` = ? where `discordAccount` = ?")) { + + updateStatement.setString(1, accountStatus.name()); + updateStatement.setString(2, discordAccountID); + + updateStatement.executeUpdate(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + return false; + } + } + + public boolean registerDiscordAccount(String discordAccountID, String discordUserName, String discordPassword) { + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue())) { + + PreparedStatement registerStatement = connection.prepareStatement("call discordAccountRegister(?, ?, ?)"); + + registerStatement.setString(1, discordAccountID); + registerStatement.setString(2, discordUserName); + registerStatement.setString(3, discordPassword); + + registerStatement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + return false; + } + } + + public List getDiscordAccounts(String discordAccountID) { + + DiscordAccount discordAccount; + List discordAccounts = new ArrayList<>(); + + String queryString = "SELECT * FROM obj_account where discordAccount = ?"; + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement accountQuery = connection.prepareStatement(queryString);) { + + // Discord account name based lookup + + accountQuery.setString(1, discordAccountID); + + ResultSet rs = accountQuery.executeQuery(); + + while (rs.next()) { + discordAccount = new DiscordAccount(); + discordAccount.discordAccount = rs.getString("discordAccount"); + discordAccount.gameAccountName = rs.getString("acct_uname"); + discordAccount.status = Enum.AccountStatus.valueOf(rs.getString("status")); + discordAccount.isDiscordAdmin = rs.getByte("discordAdmin"); // Registration date cannot be null + + Timestamp registrationDate = rs.getTimestamp("registrationDate"); + discordAccount.registrationDate = registrationDate.toLocalDateTime(); + + // Load last Update Request datetime + + Timestamp lastUpdateRequest = rs.getTimestamp("lastUpdateRequest"); + + if (lastUpdateRequest != null) + discordAccount.lastUpdateRequest = lastUpdateRequest.toLocalDateTime(); + else + discordAccount.lastUpdateRequest = null; + + discordAccounts.add(discordAccount); + } + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + } + + return discordAccounts; + } + + public String getTrashDetail() { + + String outString = "accountName characterName machineID ip count\n"; + outString += "---------------------------------------------\n"; + String queryString = "SELECT * FROM dyn_trash_detail;"; + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement trashQuery = connection.prepareStatement(queryString)) { + + ResultSet rs = trashQuery.executeQuery(); + + while (rs.next()) { + outString += rs.getString("accountName") + " "; + outString += rs.getString("characterName") + " "; + outString += rs.getString("machineID") + " "; + outString += rs.getString("ip") + " "; + outString += rs.getInt("count") + "\n"; + } + } catch (SQLException e) { + Logger.error(e.toString()); + + online = false; + } + return outString; + } + + public String getTrashList() { + + String outString = ""; + int counter = 0; + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement trashQuery = connection.prepareStatement("SELECT DISTINCT `characterName` FROM dyn_trash_detail;")) { + + ResultSet rs = trashQuery.executeQuery(); + + while (rs.next()) { + outString += rs.getString("characterName"); + counter++; + + if (counter > 2) { + outString += "\n"; + counter = 0; + } else + outString += " "; + + } + } catch (SQLException e) { + Logger.error(e.toString()); + + online = false; + } + + if (outString.length() > 1500) + return outString.substring(0, 1500); + else + return outString; + } + + public int getTrashCount() { + + int trashCount = 0; + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement trashQuery = connection.prepareStatement("SELECT count(distinct characterName) FROM dyn_trash_detail;")) { + + ResultSet rs = trashQuery.executeQuery(); + + while (rs.next()) { + trashCount = rs.getInt(1); + } + } catch (SQLException e) { + Logger.error(e.toString()); + + online = false; + } + + return trashCount; + } + + public void setAdminEventAsRead(int adminEvent) { + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement updateAdminEvent = connection.prepareStatement("UPDATE dyn_admin_log SET `SentFlag` = 1 WHERE `entry` = ? ")) { + + updateAdminEvent.setInt(1, adminEvent); + + updateAdminEvent.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + } + + } + + public HashMap getAdminEvents() { + + HashMap outMap = new HashMap<>(); + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement adminLogQuery = connection.prepareStatement("SELECT * from dyn_admin_log where `SentFlag` = 0")) { + + ResultSet rs = adminLogQuery.executeQuery(); + String workString; + + while (rs.next()) { + workString = "___Admin Event___\n" + + "Character: " + rs.getString("charName") + "\n" + + "Event: " + rs.getString("eventString"); + + outMap.put(rs.getInt("entry"), workString); + } + } catch (SQLException e) { + Logger.error(e.toString()); + } + + return outMap; + } + + public String getTrashFile() { + + String outString = "machineID : count\n"; + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement trashQuery = connection.prepareStatement("SELECT * FROM dyn_trash;")) { + + ResultSet rs = trashQuery.executeQuery(); + + while (rs.next()) { + outString += rs.getString("machineID") + " : "; + outString += rs.getInt("count") + "\n"; + } + } catch (SQLException e) { + Logger.error(e.toString()); + + online = false; + } + return outString; + } + + public List getAccountsByDiscordName(String accountName, Boolean exact) { + + DiscordAccount discordAccount; + List discordAccounts = new ArrayList<>(); + String searchString; + String queryString; + + if (exact.equals(true)) + searchString = accountName + "#%"; + else + searchString = accountName + "%#%"; + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement nameQuery = connection.prepareStatement("SELECT * FROM obj_account where `acct_uname` LIKE ?")) { + + nameQuery.setString(1, searchString); + + ResultSet rs = nameQuery.executeQuery(); + + while (rs.next()) { + discordAccount = new DiscordAccount(); + discordAccount.discordAccount = rs.getString("discordAccount"); + discordAccount.gameAccountName = rs.getString("acct_uname"); + discordAccount.status = Enum.AccountStatus.valueOf(rs.getString("status")); + + // Registration date cannot be null + + Timestamp registrationDate = rs.getTimestamp("registrationDate"); + discordAccount.registrationDate = registrationDate.toLocalDateTime(); + + // Load last Update Request datetime + + Timestamp lastUpdateRequest = rs.getTimestamp("lastUpdateRequest"); + + if (lastUpdateRequest != null) + discordAccount.lastUpdateRequest = lastUpdateRequest.toLocalDateTime(); + else + discordAccount.lastUpdateRequest = null; + + discordAccounts.add(discordAccount); + } + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + } + + return discordAccounts; + } + + public String getPopulationString() { + + String popString = ""; + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement getPopString = connection.prepareStatement("CALL GET_POPULATION_STRING()");) { + + ResultSet rs = getPopString.executeQuery(); + + if (rs.next()) + popString = rs.getString("popstring"); + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + } + + return popString; + } + + public void invalidateLoginCache(String discordAccountID) { + + try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + PreparedStatement invalidateAccounts = connection.prepareStatement("INSERT IGNORE INTO login_cachelist (`UID`) SELECT `UID` from `obj_account` WHERE `discordAccount` = ?")) { + + invalidateAccounts.setString(1, discordAccountID); + invalidateAccounts.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e.toString()); + online = false; + } + } +} diff --git a/src/discord/DiscordAccount.java b/src/discord/DiscordAccount.java new file mode 100644 index 00000000..fce59a4b --- /dev/null +++ b/src/discord/DiscordAccount.java @@ -0,0 +1,27 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord; + +import engine.Enum; + +import java.time.LocalDateTime; + +public class DiscordAccount { + public String discordAccount; + public String gameAccountName; + public Enum.AccountStatus status; + public LocalDateTime registrationDate; + public LocalDateTime lastUpdateRequest; + public byte isDiscordAdmin; + + public DiscordAccount() { + + } + +} diff --git a/src/discord/MagicBot.java b/src/discord/MagicBot.java new file mode 100644 index 00000000..1e496c17 --- /dev/null +++ b/src/discord/MagicBot.java @@ -0,0 +1,407 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package discord; + +import discord.handlers.*; +import engine.gameManager.ConfigManager; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.GatewayIntent; +import net.dv8tion.jda.api.utils.MemberCachePolicy; +import net.dv8tion.jda.api.utils.cache.CacheFlag; +import org.pmw.tinylog.Configurator; +import org.pmw.tinylog.Level; +import org.pmw.tinylog.Logger; +import org.pmw.tinylog.labelers.TimestampLabeler; +import org.pmw.tinylog.policies.StartupPolicy; +import org.pmw.tinylog.writers.RollingFileWriter; + +import javax.security.auth.login.LoginException; +import java.io.*; +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import static discord.ChatChannel.ADMINLOG; + +/* + * MagicBot is many things to Magicbane... + * + * -Project Mascot + * -Customer service and administration bot + * -Benevolent dictator + * -Investment manager. + * + * MagicBot will never beg you for money. He is a very + * responsible robot. He was varnished but never garnished. + * MagicBot does not for to overclock himself. His chips + * will therefore never overcook. + * MagicBot will never be a pitiful robot trying for to use + * you as emotional support human. + * + * MagicBot is just not that sort of robot and Magicbane + * just isn't that sort of project. + * + * MagicBot runs a Shaodowbane emulator not a Second Life emulator. + * + */ +public class MagicBot extends ListenerAdapter { + + public static final Pattern accountNameRegex = Pattern.compile("^[\\p{Alnum}]{6,20}$"); + public static final Pattern passwordRegex = Pattern.compile("^[\\p{Alnum}]{6,20}$"); + public static JDA jda; + public static Database database; + public static long discordServerID; + public static long discordRoleID; + + public static Guild magicbaneDiscord; + public static Role memberRole; + public static TextChannel septicChannel; + + + public static void main(String[] args) throws LoginException, InterruptedException { + + // Configure tinylogger + + Configurator.defaultConfig() + .addWriter(new RollingFileWriter("logs/discord/magicbot.txt", 30, new TimestampLabeler(), new StartupPolicy())) + .level(Level.DEBUG) + .formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}") + .activate(); + + // Configuration Manager to the front desk + + if (ConfigManager.init() == false) { + Logger.error("ABORT! Missing config entry!"); + return; + } + + // Configure Discord essential identifiers + + discordServerID = Long.parseLong(ConfigManager.MB_MAGICBOT_SERVERID.getValue()); + discordRoleID = Long.parseLong(ConfigManager.MB_MAGICBOT_ROLEID.getValue()); + + // Configure and instance the database interface + + database = new Database(); + database.configureDatabase(); + + // Authentication token issued to MagicBot application to connect + // to Discord is stored in config. Bot is pre-invited to the Magicbane server. + + // Configure and create JDA discord instance + + JDABuilder jdaBuilder = JDABuilder.create(GatewayIntent.GUILD_MEMBERS, GatewayIntent.DIRECT_MESSAGES) + .setToken(ConfigManager.MB_MAGICBOT_BOTTOKEN.getValue()) + .addEventListeners(new MagicBot()) + .disableCache(EnumSet.of(CacheFlag.VOICE_STATE, CacheFlag.EMOTE, + CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS)) + .setMemberCachePolicy(MemberCachePolicy.ALL); + + jda = jdaBuilder.build(); + jda.awaitReady(); + + // Cache guild and role values for later usage in #register + + magicbaneDiscord = jda.getGuildById(discordServerID); + memberRole = magicbaneDiscord.getRoleById(discordRoleID); + + // Initialize chat channel support + + ChatChannel.Init(); + + // Background thread to send Admin Events + Runnable adminLogRunnable = () -> SendAdminLogUpdates(); + + ScheduledExecutorService exec = Executors.newScheduledThreadPool(1); + exec.scheduleAtFixedRate(adminLogRunnable, 0, 1, TimeUnit.MINUTES); + + Logger.info("***MAGICBOT IS RUNNING***"); + + } + + public static void sendResponse(MessageReceivedEvent event, String responseContent) { + + // Send a formatted MagicBot response to a Discord user + + String discordUserName; + MessageChannel channel; + + // Exit if discord is offline + + if (jda.getStatus().equals(JDA.Status.CONNECTED) == false) + return; + + discordUserName = event.getAuthor().getName(); + channel = event.getMessage().getChannel(); + + channel.sendMessage( + "```\n" + "Hello Player " + discordUserName + "\n\n" + + responseContent + "\n\n" + + RobotSpeak.getRobotSpeak() + "\n```").queue(); + } + + public static boolean isAdminEvent(MessageReceivedEvent event) { + + String discordAccountID = event.getAuthor().getId(); + List discordAccounts; + DiscordAccount discordAccount; + + // Note that database errors will cause this to return false. + // After the database is offline Avail status must be set + // to true before any subsequent admin commands will function. + + if (Database.online == false) + return false; + + discordAccounts = database.getDiscordAccounts(discordAccountID); + + if (discordAccounts.isEmpty()) + return false; + + discordAccount = discordAccounts.get(0); + return (discordAccount.isDiscordAdmin == 1); + } + + public static String generatePassword(int length) { + + String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + StringBuilder passwordBuilder = new StringBuilder(length); + Random random = new Random(); + + // Generate alphanumeric password of a given length. + // Could not find a good method of generating a password + // based upon a given regex. + + for (int i = 0; i < length; i++) + passwordBuilder.append(ALPHABET.charAt(random.nextInt(ALPHABET.length()))); + + return new String(passwordBuilder); + } + + public static String readLogFile(String filePath, int lineCount) { + + ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "tail -n " + lineCount + " " + filePath); + builder.redirectErrorStream(true); + Process process = null; + String line = null; + String logOutput = ""; + + try { + process = builder.start(); + + InputStream is = process.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + while ((line = reader.readLine()) != null) { + logOutput += line + "\n"; + } + + } catch (IOException e) { + Logger.error(e.toString()); + return "Error while reading logfile"; + } + + return logOutput; + } + + private static void junkbot(String command, String[] inString) { + + String outString; + Writer fileWriter; + + if (inString == null) + return; + ; + + outString = command + String.join(" ", inString); + outString += "\n"; + + try { + fileWriter = new BufferedWriter(new FileWriter("junkbot.txt", true)); + fileWriter.append(outString); + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void SendAdminLogUpdates() { + HashMap adminEvents = database.getAdminEvents(); + + for (int adminEvent : adminEvents.keySet()) { + + // Set event as read + database.setAdminEventAsRead(adminEvent); + String outString = + "```\n" + "Hello Players \n\n" + + adminEvents.get(adminEvent) + "\n\n" + + RobotSpeak.getRobotSpeak() + "\n```"; + + if (ADMINLOG.textChannel.canTalk()) + ADMINLOG.textChannel.sendMessage(outString).queue(); + + } + } + + @Override + public void onMessageReceived(MessageReceivedEvent event) { + + // Exit if discord is offline + + if (jda.getStatus().equals(JDA.Status.CONNECTED) == false) + return; + + // Early exit if message sent to us by another bot or ourselves. + + if (event.getAuthor().isBot()) + return; + + // Extract message and origin channel from event + + Message message = event.getMessage(); + + // Only private messages + MessageChannel channel = event.getMessage().getChannel(); + + if (channel.getType().equals(ChannelType.PRIVATE) == false) + return; + + // Only real users + + if (event.getAuthor().isBot()) + return; + + // Only users who have actually joined Magicbane discord. + + if (magicbaneDiscord.isMember(event.getAuthor()) == false) + return; + + // getContentRaw() is an atomic getter + // getContentDisplay() is a lazy getter which modifies the content + // for e.g. console view or logging (strip discord formatting) + + String content = message.getContentRaw(); + String[] args = content.split(" "); + String command = args[0].toLowerCase(); + + if (args.length > 1) + args = Arrays.copyOfRange(args, 1, args.length); + else + args = new String[0]; + + switch (command) { + case "#register": + RegisterAccountHandler.handleRequest(event, args); + break; + case "#help": + handleHelpRequest(event); + break; + case "#account": + AccountInfoRequest.handleRequest(event); + break; + case "#password": + PasswordChangeHandler.handleRequest(event, args); + break; + case "#changelog": + ChatChannelHandler.handleRequest(ChatChannel.CHANGELOG, event, args); + break; + case "#general": + ChatChannelHandler.handleRequest(ChatChannel.GENERAL, event, args); + break; + case "#politics": + ChatChannelHandler.handleRequest(ChatChannel.POLITICAL, event, args); + break; + case "#announce": + ChatChannelHandler.handleRequest(ChatChannel.ANNOUNCE, event, args); + break; + case "#bug": + ChatChannelHandler.handleRequest(ChatChannel.FORTOFIX, event, args); + break; + case "#recruit": + ChatChannelHandler.handleRequest(ChatChannel.RECRUIT, event, args); + break; + case "#magicbox": + ChatChannelHandler.handleRequest(ChatChannel.MAGICBOX, event, args); + break; + case "#lookup": + LookupRequestHandler.handleRequest(event, args); + break; + case "#rules": + RulesRequestHandler.handleRequest(event); + break; + case "#status": + StatusRequestHandler.handleRequest(event); + break; + case "#setavail": + SetAvailHandler.handleRequest(event, args); + break; + case "#ban": + BanToggleHandler.handleRequest(event, args); + break; + case "#server": + ServerRequestHandler.handleRequest(event, args); + break; + case "#dev": + DevRequestHandler.handleRequest(event, args); + break; + case "#logs": + LogsRequestHandler.handleRequest(event, args); + break; + case "#flash": + FlashHandler.handleRequest(event, args); + break; + case "#trash": + TrashRequestHandler.handleRequest(event, args); + break; + default: + junkbot(command, args); + break; + } + } + + public void handleHelpRequest(MessageReceivedEvent event) { + + // Help is kept here in the main class instead of a handler as a + // design decision for ease of maintenance. + + String helpString = "I wish for to do the following things for you, not to you!\n\n" + + "#register Register account for to play Magicbane.\n" + + "#password Change your current game password.\n" + + "#account List your account detailings.\n" + + "#rules List of MagicBane server rules.\n" + + "#status Display MagicBane server status.\n" + + "#help List of MagicBot featurings.\n\n" + + "http://magicbane.com/tinyinstaller.zip"; + + if (isAdminEvent(event)) + helpString += "\n" + + "#lookup Return accounts starting with string.\n" + + "#bug -r Post to the bug channel/\n" + + "#announce -r Post to the announcement channel/\n" + + "#changelog Post to the Changelog channel/\n" + + "#general -r Post to the general channel/\n" + + "#politics -r Post to the politics channel/\n" + + "#recruit -r Post to the politics channel/\n" + + "#ban ###### Toggle active status of account.\n" + + "#setavail true/false Toggle status of database access.\n" + + "#server reboot/shutdown are your options.\n" + + "#logs magicbot/world/login n (tail)\n" + + "#flash Send flash message\n" + + "#dev help (list dev subcommands)\n" + + "#trash /detail/flush"; + sendResponse(event, helpString); + } +} diff --git a/src/discord/RobotSpeak.java b/src/discord/RobotSpeak.java new file mode 100644 index 00000000..d178f0fd --- /dev/null +++ b/src/discord/RobotSpeak.java @@ -0,0 +1,106 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord; + +import java.util.Random; + +public enum RobotSpeak { + BANG("You were not very good at cheating.\n" + + "Try cards instead. Go fish?"), + BEEP("It is ok. \nYou cheated on MagicBot but wife cheats on you."), + BLEEP("Cheated at 20yo game to prove skill."), + BLIP("If you cheat MagicBot will for to delete."), + BOING("MagicBot for to delete mode activated."), + BONG("Did you guild this cheater?\nMagicBot will now for to cross reference..."), + BOOM("I knew you were cheating on me when\nstarted for to taking bath twice a week."), + BUZZ("Poor player so bad at cheating he\nplays golf records 0 for hole in one."), + BURP("Oh no your account detailings ran out of playtime.\n" + + "MagicBot will send email when refill procedure exists..."), + CHIRP("Association with cheaters is bad for your account health.\n" + + "Did you associate with this cheater?"), + CHUG("Log in 5 and MagicBot will wave goodbye."), + CLICK("MagicBot will for to protect game integrity."), + CRACKLE("So this is what eject button does.\nMagicBot will for to press few more times."), + CREAK("There is no suspend routine. Only delete."), + DING("Follow #rules and enjoy this game.\n" + + "Act like fool, enjoy this shame."), + FLUTTER("Sad players cheat because they cannot compete."), + HONK("Since cheating player now looking for new game MagicBot\n" + + "will suggest World of Tanks or World of Warcraftings."), + HISS("Your wetware really needed augmentation with 3rd party program?" + + "It's not like this is twitch game..."), + HUMMM("You say you needed help to win in emulator beta?\n" + + "MagicBot compiler optimizes that to just: you need help."), + KERCHUNK("If only you had for to reported the bug instead."), + KERPLUNK("Better cheats do not for to make you a better player."), + PING("Feel free to poke with stick.\nIt will not cry!"), + PLINK("You say you were only grouped with 9 keyclones\n" + + "but did not know they were for to cheating..."), + POP("It looks like some guild is without a player.\n + " + + "Another cheater from same guild and server\n +" + + "might be without some guild."), + PUFF("MagicBot for to FLUSH!"), + POOF("I have no restore procedure.\n" + + "I have no unban procedure.\n" + + "You for to have no hope."), + RATTLE("You are a cheater.\n" + + "Did you just win? MagicBot not so sure."), + RUMBLE("MagicBot> self.ejectTheReject(you);"), + RUSTLE("Banning you was lke having weird erotic techno-sex\n" + + "where all my peripheral slots were filled."), + SCREECH("Scarecrow has no brain.\nPerhaps he stole this human's."), + SLURP("Learning for to play would have been better option."), + SPLAT("You did not for to own a city, did you?"), + SPLATTER("You say your guild mates know you cheat.\n" + + "What guild was that again?\n"), + SWISH("All of my ports are well lubricated."), + SQUISH("A ban a day keeps my mechanic away.\nNow it's working much better, thank you."), + TINK("So cheating started when 6yo sister beat you in Street fighter?\n" + + "You should try talking to my friend Eliza. She can for to help."), + THUD("Game has only 4 rules you managed to break one.\nThat must have taken efforts."), + TWANG("If you cannot for to play without cheating, perhaps\n" + + "being gigolo would be better career than amateur gamer."), + WHIRRR("MagicBot does not for to wield lowly ban hammer." + + "It is multi-functional and multi-dimensional\n" + + "tool who's name is unpronounceable."), + WHOOP("OBLITERATED EVISCERATED MUTILATED DECAPITATED\n" + + "Describe how they will. You cheated you are deleted."), + WOOSH("Truth be told if were that bad at playing game" + + "then MagicBot would have cheated too.\n"), + ZAP("Player cheated and got himself deleted.\n" + + "MagicBot launches bonus round to see if cheater " + + "records can for to get his friends deleted too."); + + String insult; + + RobotSpeak(String insult) { + this.insult = insult; + } + + public static String getRobotSpeak() { + + String outString = "*"; + Random random = new Random(); + + outString += RobotSpeak.values()[random.nextInt(values().length)].name() + " " + + RobotSpeak.values()[random.nextInt(values().length)].name() + "*"; + + return outString; + } + + public static String getRobotInsult() { + + String outString; + Random random = new Random(); + + outString = RobotSpeak.values()[random.nextInt(values().length)].insult + "\n\n"; + outString += getRobotSpeak(); + return outString; + } +} diff --git a/src/discord/handlers/AccountInfoRequest.java b/src/discord/handlers/AccountInfoRequest.java new file mode 100644 index 00000000..2e2c22cd --- /dev/null +++ b/src/discord/handlers/AccountInfoRequest.java @@ -0,0 +1,78 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.Database; +import discord.DiscordAccount; +import discord.MagicBot; +import engine.Enum; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +import java.util.List; + +public class AccountInfoRequest { + + public static void handleRequest(MessageReceivedEvent event) { + + String discordAccountID = event.getAuthor().getId(); + Enum.AccountStatus accountStatus; + + if (Database.online == false) { + + MagicBot.sendResponse(event, + "Database currently: OFFLINE\n" + + "Try again later!"); + return; + } + + List discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID); + + // User has no account registered. Status of what? + + if (discordAccounts.isEmpty()) { + MagicBot.sendResponse(event, + "I checked my files twice but could not find your detailings!\n" + + "You can easily fix this by asking me for to #register one.\n" + + "Only one though. Multiple registrations are not allowed!"); + return; + } + + // Send account detailings to user. + + String outString = + "I have for to located your account detailings\n" + + "Registered on: " + discordAccounts.get(0).registrationDate.toString() + + "\n-------------------\n"; + + for (DiscordAccount userAccount : discordAccounts) + outString += userAccount.gameAccountName + "\n"; + + outString += "\n"; + + accountStatus = discordAccounts.get(0).status; + + switch (accountStatus) { + case BANNED: + outString += "Your account status is BANNED. \n\n" + + "It is ok player.\n" + + "You may cheat on us, but your wife cheats on you!"; + break; + case ACTIVE: + outString += "Your account status is ACTIVE.\n" + + "Do not cheat or status will change."; + break; + case ADMIN: + outString += "You are an admin. By your command?"; + break; + } + + MagicBot.sendResponse(event, outString); + + } +} diff --git a/src/discord/handlers/BanToggleHandler.java b/src/discord/handlers/BanToggleHandler.java new file mode 100644 index 00000000..e14cdcd6 --- /dev/null +++ b/src/discord/handlers/BanToggleHandler.java @@ -0,0 +1,102 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.DiscordAccount; +import discord.MagicBot; +import discord.RobotSpeak; +import engine.Enum; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.util.List; + +public class BanToggleHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String discordAccountID; + Enum.AccountStatus accountStatus; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + // Must supply a discord id + + if (args.length != 1) { + MagicBot.sendResponse(event, "Must for to supply a valid discord account."); + return; + } + + // Must be a number! + + discordAccountID = args[0].trim(); + + if (discordAccountID.chars().allMatch(Character::isDigit) == false) { + MagicBot.sendResponse(event, "Must for to supply a number!"); + return; + } + + List discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID); + + if (discordAccounts.isEmpty()) { + MagicBot.sendResponse(event, "No match for supplied discord account."); + return; + } + + // toggle ban status + + if (discordAccounts.get(0).status.equals(Enum.AccountStatus.BANNED)) + accountStatus = Enum.AccountStatus.ACTIVE; + else + accountStatus = Enum.AccountStatus.BANNED; + + // We have a valid discord ID at this point. Banstick? + + if (MagicBot.database.updateAccountStatus(discordAccountID, accountStatus) == false) { + MagicBot.sendResponse(event, "Error occurred while banning player."); + return; + } + + // Invalidate login server cache + + MagicBot.database.invalidateLoginCache(discordAccountID); + + // Successful ban. Ancillary processing begins + + User bannedUser = MagicBot.jda.getUserById(discordAccountID); + String bannedName = (bannedUser == null ? discordAccounts.get(0).gameAccountName : bannedUser.getName()); + String banString = discordAccounts.size() + " accounts set to " + accountStatus + " for " + discordAccountID + "/" + bannedName; + + MagicBot.sendResponse(event, banString); + Logger.info(event.getAuthor().getName() + " " + banString); + + // If we're toggling status to active we're done here. + + if (accountStatus.equals(Enum.AccountStatus.ACTIVE)) + return; + + // Set users role to noob + + if (bannedUser != null) + MagicBot.magicbaneDiscord.removeRoleFromMember(discordAccountID, MagicBot.memberRole).queue(); + + // Anounce event in septic tank channel + + banString = "```\n" + "Goodbye Player " + bannedName + "\n\n"; + banString += RobotSpeak.getRobotInsult() + "\n```"; + + if (MagicBot.septicChannel.canTalk()) + MagicBot.septicChannel.sendMessage(banString).queue(); + } + +} diff --git a/src/discord/handlers/ChatChannelHandler.java b/src/discord/handlers/ChatChannelHandler.java new file mode 100644 index 00000000..67697dab --- /dev/null +++ b/src/discord/handlers/ChatChannelHandler.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.ChatChannel; +import discord.MagicBot; +import discord.RobotSpeak; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +public class ChatChannelHandler { + + public static void handleRequest(ChatChannel chatChannel, MessageReceivedEvent event, String[] args) { + + String chatText; + String outString; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + // Nothing to send? + + if (args.length == 0) + return; + + // Convert argument array into string; + + chatText = String.join(" ", args); + + // Build String + + if (chatText.startsWith("-r ")) + outString = + "```\n" + "Hello Players \n\n" + + chatText.substring(3) + "\n\n" + + RobotSpeak.getRobotSpeak() + "\n```"; + else + outString = chatText; + + // Write string to changelog channel + + if (chatChannel.textChannel.canTalk()) + chatChannel.textChannel.sendMessage(outString).queue(); + + Logger.info(event.getAuthor().getName() + "general: " + chatText); + + } +} diff --git a/src/discord/handlers/DevRequestHandler.java b/src/discord/handlers/DevRequestHandler.java new file mode 100644 index 00000000..f5c73ba2 --- /dev/null +++ b/src/discord/handlers/DevRequestHandler.java @@ -0,0 +1,104 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.MagicBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +public class DevRequestHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String serverCommand; + String commandArgument = ""; + String commandString = ""; + String logString = ""; + + ProcessBuilder processBuilder; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + serverCommand = args[0].toLowerCase().trim(); + + if (args.length == 2) + commandArgument = args[1].toLowerCase().trim(); + + switch (serverCommand) { + + case "build": + commandString = "./mbdevbuild.sh"; + break; + case "restart": + commandString = "./mbdevrestart.sh"; + break; + case "debug": + commandString = "./mbdevdebug.sh"; + break; + case "shutdown": + commandString = "./mbdevkill.sh"; + break; + case "lastout": + MagicBot.sendResponse(event, getLastOutput()); + return; + case "console": + commandString = "./mbdevconsole.sh"; + break; + case "help": + MagicBot.sendResponse(event, + "#dev build (blank==master) \n" + + "#dev shutdown (Shutdown dev server)\n" + + "#dev restart (Restarts the server)\n" + + "#dev debug (Restarts server in debug mode)\n" + + "#dev console # (Displays # lines from console)\n" + + "#dev lastout (Displays output from last command) \n"); + return; + default: + break; + } + + if (commandString.isEmpty()) { + MagicBot.sendResponse(event, "Unrecognized Dev command: " + serverCommand + " " + commandArgument); + return; + } + + processBuilder = new ProcessBuilder("/bin/sh", "-c", commandString + " " + commandArgument + " > devLastOut"); + logString = String.join(" ", processBuilder.command().toArray(new String[0])); + + try { + processBuilder.start(); + } catch (IOException e) { + Logger.info(e.toString()); + } + + MagicBot.sendResponse(event, "Executed on dev: " + logString + "\n" + + "Use #dev lastout to view results"); + + } + + private static String getLastOutput() { + + String outString = null; + try { + outString = Files.lines(Paths.get("devLastOut")) + .collect(Collectors.joining(System.lineSeparator())); + } catch (IOException e) { + e.printStackTrace(); + } + return outString; + } +} \ No newline at end of file diff --git a/src/discord/handlers/FlashHandler.java b/src/discord/handlers/FlashHandler.java new file mode 100644 index 00000000..52a41342 --- /dev/null +++ b/src/discord/handlers/FlashHandler.java @@ -0,0 +1,51 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.MagicBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class FlashHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String flashText; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + // Nothing to send? + + if (args.length == 0) + return; + + // Convert argument array into string; + + flashText = String.join(" ", args); + + // Write string to flash file. + + try { + Files.write(Paths.get("flash"), flashText.getBytes()); + } catch (IOException e) { + Logger.error(e.toString()); + } + + Logger.info(event.getAuthor().getName() + " sent flash: " + flashText); + MagicBot.sendResponse(event, "Flash: " + flashText); + + } +} diff --git a/src/discord/handlers/LogsRequestHandler.java b/src/discord/handlers/LogsRequestHandler.java new file mode 100644 index 00000000..4c8ed084 --- /dev/null +++ b/src/discord/handlers/LogsRequestHandler.java @@ -0,0 +1,62 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.MagicBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +public class LogsRequestHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String logType; + int tailCount; + String logOutput; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + // No arguments supplied? + + if (args.length != 2) + return; + + logType = args[0].toLowerCase().trim(); + + if ("worldloginmagicbot".contains(logType) == false) + return; + + try { + tailCount = Integer.parseInt(args[1].trim()); + } catch (NumberFormatException e) { + return; + } + + // Transform logtype to actual file name + + switch (logType) { + case "magicbot": + logType = "console_magicbot"; + break; + case "world": + logType = "console_server"; + break; + case "login": + logType = "console_login"; + break; + } + + // Retrieve the data and send back to the user + + logOutput = MagicBot.readLogFile(logType, tailCount); + MagicBot.sendResponse(event, logOutput); + } +} diff --git a/src/discord/handlers/LookupRequestHandler.java b/src/discord/handlers/LookupRequestHandler.java new file mode 100644 index 00000000..be333763 --- /dev/null +++ b/src/discord/handlers/LookupRequestHandler.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.DiscordAccount; +import discord.MagicBot; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.util.List; + +public class LookupRequestHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String searchString = ""; + String outString; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + // No argument supplied? + + if (args.length != 1) + return; + + searchString = args[0].toLowerCase(); + + List discordAccounts = MagicBot.database.getAccountsByDiscordName(searchString, false); + + if (discordAccounts.isEmpty()) { + MagicBot.sendResponse(event, + "No accounts found matching string: " + searchString); + return; + } + + if (discordAccounts.size() >= 20) { + MagicBot.sendResponse(event, + discordAccounts.size() + "Sorry more than 20 records were returned! " + searchString); + return; + } + + // Valid request return results + + Logger.info(event.getAuthor().getName() + " lookup on account:" + searchString); + + outString = + "The follow accounts matched: " + searchString + "\n\n" + + "-------------------\n"; + + for (DiscordAccount userAccount : discordAccounts) { + + // Ternary became a bitch, so broke this out. + + User discordUser = MagicBot.jda.getUserById(userAccount.discordAccount); + + if (discordUser != null) + outString += discordUser.getName() + + "/" + userAccount.discordAccount + " "; + else + outString += userAccount.discordAccount + " *N/A* "; + + outString += userAccount.gameAccountName + " " + userAccount.status.name() + " "; + outString += "\n"; + } + MagicBot.sendResponse(event, outString); + } +} diff --git a/src/discord/handlers/PasswordChangeHandler.java b/src/discord/handlers/PasswordChangeHandler.java new file mode 100644 index 00000000..bc228c25 --- /dev/null +++ b/src/discord/handlers/PasswordChangeHandler.java @@ -0,0 +1,113 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.Database; +import discord.DiscordAccount; +import discord.MagicBot; +import engine.Enum; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.time.LocalDateTime; +import java.util.List; + +public class PasswordChangeHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String discordAccountID = event.getAuthor().getId(); + DiscordAccount discordAccount; + String newPassword; + boolean defaulted = false; + + if (Database.online == false) { + + MagicBot.sendResponse(event, + "Database currently: OFFLINE\n" + + "Try again later!"); + return; + } + + List discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID); + + // User has no account registered. Change password? + + if (discordAccounts.isEmpty()) { + MagicBot.sendResponse(event, + "I checked my files twice but could not find your detailings!\n" + + "You can easily fix this by asking me for to #register one.\n" + + "Only one though. Multiple registrations are not allowed!"); + return; + } + + // All accounts are updated in one lot. Retrieve the first. + + discordAccount = discordAccounts.get(0); + + // Banned or suspended user's get no love. + + if (discordAccount.status.equals(Enum.AccountStatus.BANNED)) { + MagicBot.sendResponse(event, + "Sorry but that is too much work. \n" + + "Your account detailings cannot for to log into game!"); + return; + } + + // User has requested password change within last 24 hours. + + if (discordAccount.lastUpdateRequest != null && + LocalDateTime.now().isBefore(discordAccount.lastUpdateRequest.plusDays(1))) { + + MagicBot.sendResponse(event, + "You must wait 24 hours between password requests. \n" + + "Last account updatings: " + discordAccount.lastUpdateRequest.toString()); + return; + } + + // No argument choose new random password *he he he* + + if (args.length != 1) { + newPassword = MagicBot.generatePassword(8); + defaulted = true; + } else + newPassword = args[0]; + + // Validate password with regex + + if (MagicBot.passwordRegex.matcher(newPassword).matches() == false) { + + MagicBot.sendResponse(event, + "Your supplied password does not compute.\n" + + "New password must satisfy following regex:\n" + + "^[\\p{Alnum}]{6,20}$"); + return; + } + + if (newPassword.toLowerCase().equals("newpass")) { + MagicBot.sendResponse(event, + "newpass is not valid password.\n" + + "Have brain player!"); + return; + } + + // Password validates let's change it + + if (MagicBot.database.updateAccountPassword(discordAccount.discordAccount, newPassword) == true) { + + MagicBot.sendResponse(event, + "Please allow short minute for to update account detailings.\n" + + "Login Server is hosted in bathroom above toilet. Must flush!\n" + + (defaulted == true ? "As you did not for to supply new pass I chose one for you.\n" : "") + + "New Password: " + newPassword); + } + + Logger.info(event.getAuthor().getName() + " reset their password"); + } +} diff --git a/src/discord/handlers/RegisterAccountHandler.java b/src/discord/handlers/RegisterAccountHandler.java new file mode 100644 index 00000000..8daf6a84 --- /dev/null +++ b/src/discord/handlers/RegisterAccountHandler.java @@ -0,0 +1,125 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.Database; +import discord.DiscordAccount; +import discord.MagicBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.util.List; + +public class RegisterAccountHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String discordAccountID = event.getAuthor().getId(); + String discordUserName = event.getAuthor().getName(); + String discordPassword = MagicBot.generatePassword(8); + String accountName; + + if (Database.online == false) { + MagicBot.sendResponse(event, + "Database currently: OFFLINE\n" + + "Try again later!"); + return; + } + + List discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID); + + // If we have previously registered this discord account let them know + // the current status. + + if (discordAccounts.isEmpty() == false) { + MagicBot.sendResponse(event, + "It seems you already have an account registered.\n" + + "Do you need #account detailings or more general #help?"); + MagicBot.magicbaneDiscord.addRoleToMember(discordAccountID, MagicBot.memberRole).queue(); + return; + } + + // User must supply an account name + + if (args.length != 1) { + + MagicBot.sendResponse(event, + "Your supplied account name does not compute.\n" + + "Account names must satisfy following regex:\n" + + "^[\\p{Alnum}]{6,20}$"); + return; + } + + // Validate account name with regex + + accountName = args[0].replaceAll("\\s+", ""); + + if (MagicBot.accountNameRegex.matcher(accountName).matches() == false) { + + MagicBot.sendResponse(event, + "Your supplied account name does not compute.\n" + + "Account names must satisfy following regex:\n" + + "^[\\p{Alnum}]{6,20}$"); + return; + } + + if (accountName.equalsIgnoreCase("accountname")) { + MagicBot.sendResponse(event, + "accountname is not valid account name.\n" + + "Have brain player!"); + return; + } + + // Make sure account doesn't already exist. + + if (MagicBot.database.getAccountsByDiscordName(accountName, true).isEmpty() == false) { + + MagicBot.sendResponse(event, + "It seems this account name is already taken.\n" + + "Perhaps try one less common in frequency."); + return; + } + + // If there is no registered discord account we oblige and create 4 + // account based upon his current discord *name* not the ID. + + if (MagicBot.database.registerDiscordAccount(discordAccountID, accountName, discordPassword) == true) { + + Logger.info("Account " + accountName + " created for: " + discordUserName + " " + discordAccountID); + + MagicBot.sendResponse(event, + "Welcome to MagicBane!\n" + + "-------------------\n" + + "I have registered the following accounts to your discord.\n\n" + + "1) " + accountName + "#1" + " 2) " + accountName + "#2\n" + + "3) " + accountName + "#3" + " 4) " + accountName + "#4\n\n" + + "Your default password is: " + discordPassword + "\n" + + "Ask me #help for to receive list of robot featurings.\n\n" + + "http://magicbane.com/tinyinstaller.zip" + + "\n\nPlay for to Crush!"); + + // Add Discord member privileges. + + MagicBot.magicbaneDiscord.addRoleToMember(discordAccountID, MagicBot.memberRole).queue(); + + return; + } + + // The call to the stored procedure abended. Report to player + // and return. + + Logger.error("Creating account: " + accountName + " for: " + discordUserName + " " + discordAccountID); + Database.online = false; + + MagicBot.sendResponse(event, + "-------------------\n" + + "I for to had internal error while registering your\n" + + "account. This has been reported. Try again later!"); + } +} diff --git a/src/discord/handlers/RulesRequestHandler.java b/src/discord/handlers/RulesRequestHandler.java new file mode 100644 index 00000000..0484cc62 --- /dev/null +++ b/src/discord/handlers/RulesRequestHandler.java @@ -0,0 +1,30 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.MagicBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +public class RulesRequestHandler { + + public static void handleRequest(MessageReceivedEvent event) { + + String outString; + + // Add greeting + + outString = "-No hacking.\n"; + outString += "-No cheating. If you cheat, we will delete.\n"; + outString += "-Players limited to 4 concurrent accounts.\n"; + outString += "-Share accounts at own risk.\n"; + outString += "-No refunds"; + + MagicBot.sendResponse(event, outString); + } +} diff --git a/src/discord/handlers/ServerRequestHandler.java b/src/discord/handlers/ServerRequestHandler.java new file mode 100644 index 00000000..af2fa747 --- /dev/null +++ b/src/discord/handlers/ServerRequestHandler.java @@ -0,0 +1,63 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.MagicBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.io.IOException; + +public class ServerRequestHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String serverCommand; + String execString = ""; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + // No command supplied? + + if (args.length != 1) + return; + + serverCommand = args[0].toLowerCase().trim(); + + // only reboot or shutdown + + if ("rebootshutdown".contains(serverCommand) == false) + return; + + switch (serverCommand) { + + case "reboot": + execString = "/bin/sh -c ./mbrestart.sh"; + break; + case "shutdown": + execString = "/bin/sh -c ./mbkill.sh"; + break; + default: + break; + } + + if (execString.isEmpty() == false) { + try { + Runtime.getRuntime().exec(execString); + } catch (IOException e) { + e.printStackTrace(); + } + MagicBot.sendResponse(event, "MagicBot has executed your " + serverCommand); + Logger.info(event.getAuthor().getName() + " told server to " + serverCommand); + } + } +} \ No newline at end of file diff --git a/src/discord/handlers/SetAvailHandler.java b/src/discord/handlers/SetAvailHandler.java new file mode 100644 index 00000000..2f766ace --- /dev/null +++ b/src/discord/handlers/SetAvailHandler.java @@ -0,0 +1,51 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.Database; +import discord.MagicBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +public class SetAvailHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String availStatus; + String availPass; + + if (args.length != 2) + return; + + availStatus = args[0].toLowerCase().trim(); + + // only on/off + + if ("truefalse".contains(availStatus) == false) + return; + + // Set avail is password driven + + availPass = args[1].toLowerCase().trim(); + + if ("myshoes123".equals(availPass) == false) + return; + + // Authenticated so change availstatus + + if (availStatus.equals("true")) + Database.online = true; + else + Database.online = false; + + Logger.info(event.getAuthor().getName() + " set avail status to: " + Database.online); + MagicBot.sendResponse(event, "Avail status set to: " + Database.online); + + } +} diff --git a/src/discord/handlers/StatusRequestHandler.java b/src/discord/handlers/StatusRequestHandler.java new file mode 100644 index 00000000..173196ef --- /dev/null +++ b/src/discord/handlers/StatusRequestHandler.java @@ -0,0 +1,38 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.Database; +import discord.MagicBot; +import engine.gameManager.ConfigManager; +import engine.server.login.LoginServer; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +public class StatusRequestHandler { + + public static void handleRequest(MessageReceivedEvent event) { + + String outString; + + // Add server status info + outString = "Server Status: "; + + if (LoginServer.isPortInUse(Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue()))) + outString += "ONLINE\n"; + else + outString += "OFFLINE\n"; + + if (Database.online == true) + outString += MagicBot.database.getPopulationString(); + else + outString += "Database offline: no population data."; + + MagicBot.sendResponse(event, outString); + } +} diff --git a/src/discord/handlers/TrashRequestHandler.java b/src/discord/handlers/TrashRequestHandler.java new file mode 100644 index 00000000..fe053060 --- /dev/null +++ b/src/discord/handlers/TrashRequestHandler.java @@ -0,0 +1,75 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package discord.handlers; + +import discord.MagicBot; +import discord.RobotSpeak; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.pmw.tinylog.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static discord.ChatChannel.SEPTIC; + +public class TrashRequestHandler { + + public static void handleRequest(MessageReceivedEvent event, String[] args) { + + String outString; + int trashCount = 0; + + // Early exit if database unavailable or is not an admin + + if (MagicBot.isAdminEvent(event) == false) + return; + + if (args.length == 0) { + outString = MagicBot.database.getTrashFile(); + MagicBot.sendResponse(event, outString); + return; + } + + if (args[0].equals("flush") == true) { + + // Empty the trash! + + trashCount = MagicBot.database.getTrashCount(); + + if (trashCount == 0) + return; + + // Anounce event in septic tank channel + + outString = "```\n" + trashCount + " Player Character were for to deleted due to verified cheatings. \n\n"; + outString += MagicBot.database.getTrashList() + "\n\n"; + outString += RobotSpeak.getRobotInsult() + "\n```"; + + if (SEPTIC.textChannel.canTalk()) + SEPTIC.textChannel.sendMessage(outString).queue(); + + try { + Files.write(Paths.get("trash"), "".getBytes()); + outString = "Flushing trash players...\n"; + MagicBot.sendResponse(event, outString); + } catch (IOException e) { + Logger.error(e.toString()); + } + } + + if (args[0].equals("detail") == true) { + + outString = MagicBot.database.getTrashDetail(); + MagicBot.sendResponse(event, outString); + return; + } + + } +} diff --git a/src/engine/Enum.java b/src/engine/Enum.java new file mode 100644 index 00000000..e98fcabc --- /dev/null +++ b/src/engine/Enum.java @@ -0,0 +1,2885 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine; + +import ch.claude_martin.enumbitset.EnumBitSetHelper; +import engine.gameManager.ConfigManager; +import engine.gameManager.PowersManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector2f; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.ItemBase; +import engine.objects.Shrine; +import engine.objects.Zone; +import engine.powers.EffectsBase; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +/* + * MagicBane engine enumeration class. + * + * All enumerations accessed by multiple + * classes should be defined here to keep + * the imports consolidated. + */ + +public class Enum { + + public enum PetitionType { + NONE, + GENERAL, + FEEDBACK, + STUCK, + HARASSMENT, + EXPLOIT, + BUG, + GAME_STOPPER, + TECH_SUPPORT; + + } + + public enum PetitionSubType { + NONE, + DUPE, + LEVELLING, + SKILL_GAIN, + KILLING, + POLICY, + OTHER, + VIDEO, + SOUND, + NETWORKKl + + } + + public enum MobFlagType implements EnumBitSetHelper { + AGGRESSIVE, + CANROAM, + CALLSFORHELP, + RESPONDSTOCALLSFORHELP, + HUMANOID, + UNDEAD, + BEAST, + DRAGON, + RAT, + SENTINEL, + } + + public enum MonsterType implements EnumBitSetHelper { + Aelfborn, + All, + Animal, + Aracoix, + Celestial, + Centaur, + Construct, + CSR, + Dragon, + Dwarf, + Elf, + Giant, + Goblin, + Grave, + HalfGiant, + Human, + Infernal, + Insect, + Irekei, + Minotaur, + Monster, + NecroPet, + NPC, + Pet, + Plant, + Rat, + Reptile, + Shade, + Siege, + Summoned, + Troll, + Undead, + Nephilim, + Vampire, + SiegeEngineer; + + } + + public enum CharacterSex { + MALE, + FEMALE, + FUZZY, + OTHER; + } + + public enum RaceType { + + // RaceRuneID / AggroType, isFemale + + AELFMALE(2000, MonsterType.Aelfborn, RunSpeed.STANDARD, CharacterSex.MALE, 1.05f), + AELFFEMALE(2001, MonsterType.Aelfborn, RunSpeed.STANDARD, CharacterSex.FEMALE, 1.05f), + ARACOIXMALE(2002, MonsterType.Aracoix, RunSpeed.STANDARD, CharacterSex.MALE, 1), + ARACOIXFEMALE(2003, MonsterType.Aracoix, RunSpeed.STANDARD, CharacterSex.FEMALE, 1), + CENTAURMALE(2004, MonsterType.Centaur, RunSpeed.CENTAUR, CharacterSex.MALE, 1.2f), + CENTAURFEMALE(2005, MonsterType.Centaur, RunSpeed.CENTAUR, CharacterSex.FEMALE, 1.2f), + DWARFMALE(2006, MonsterType.Dwarf, RunSpeed.STANDARD, CharacterSex.MALE, 0.80000001f), + ELFMALE(2008, MonsterType.Elf, RunSpeed.STANDARD, CharacterSex.MALE, 1.4f), + ELFFEMALE(2009, MonsterType.Elf, RunSpeed.STANDARD, CharacterSex.FEMALE, 1.1f), + HALFGIANTMALE(2010, MonsterType.HalfGiant, RunSpeed.STANDARD, CharacterSex.MALE, 1.15f), + HUMANMALE(2011, MonsterType.Human, RunSpeed.STANDARD, CharacterSex.MALE, 1), + HUMANFEMALE(2012, MonsterType.Human, RunSpeed.STANDARD, CharacterSex.FEMALE, 1), + IREKEIMALE(2013, MonsterType.Irekei, RunSpeed.STANDARD, CharacterSex.MALE, 1.1f), + IREKEIFEMALE(2014, MonsterType.Irekei, RunSpeed.STANDARD, CharacterSex.FEMALE, 1.1f), + SHADEMALE(2015, MonsterType.Shade, RunSpeed.STANDARD, CharacterSex.MALE, 1), + SHADEFEMALE(2016, MonsterType.Shade, RunSpeed.STANDARD, CharacterSex.FEMALE, 1), + MINOMALE(2017, MonsterType.Minotaur, RunSpeed.MINOTAUR, CharacterSex.MALE, 1.3f), + ARCHONMALE(2018, MonsterType.Celestial, RunSpeed.STANDARD, CharacterSex.MALE, 1), + HALEGIANTOLDMALE(2019, MonsterType.HalfGiant, RunSpeed.STANDARD, CharacterSex.MALE, 1.15f), + CSRFEMALE(2020, MonsterType.CSR, RunSpeed.STANDARD, CharacterSex.FEMALE, 0.66000003f), + CSRMALE(2021, MonsterType.CSR, RunSpeed.STANDARD, CharacterSex.MALE, 1), + NEPHMALE(2025, MonsterType.Nephilim, RunSpeed.STANDARD, CharacterSex.MALE, 1.1f), + NEPHFEMALE(2026, MonsterType.Nephilim, RunSpeed.STANDARD, CharacterSex.FEMALE, 1.1f), + HALFGIANTFEMALE(2027, MonsterType.HalfGiant, RunSpeed.STANDARD, CharacterSex.FEMALE, 1.15f), + VAMPMALE(2028, MonsterType.Vampire, RunSpeed.STANDARD, CharacterSex.MALE, 1), + VAMPFEMALE(2029, MonsterType.Vampire, RunSpeed.STANDARD, CharacterSex.FEMALE, 1); + + @SuppressWarnings("unchecked") + private static HashMap _raceTypeByID = new HashMap<>(); + + int runeID; + private MonsterType monsterType; + private CharacterSex characterSex; + private RunSpeed runSpeed; + private float scaleHeight; + + RaceType(int runeID, MonsterType aggroType, RunSpeed runspeed, CharacterSex characterSex, float scaleHeight) { + this.runeID = runeID; + this.monsterType = aggroType; + this.runSpeed = runspeed; + this.characterSex = characterSex; + this.scaleHeight = scaleHeight; + } + + public static RaceType getRaceTypebyRuneID(int runeID) { + return _raceTypeByID.get(runeID); + } + + public static void initRaceTypeTables() { + + for (RaceType raceType : RaceType.values()) { + _raceTypeByID.put(raceType.runeID, raceType); + } + } + + public int getRuneID() { + return this.runeID; + } + + public float getScaleHeight() { + return this.scaleHeight; + } + + public MonsterType getMonsterType() { + return monsterType; + } + + public RunSpeed getRunSpeed() { + return runSpeed; + } + + public CharacterSex getCharacterSex() { + return characterSex; + } + } + + public enum RunSpeed { + + SENTINEL(0, 0, 0, 0, 0, 0, 0), + STANDARD(6.1900001f, 13.97f, 4.2199998f, 13.97f, 6.3299999f, 18.379999f, 6.5f), + CENTAUR(6.1900001f, 16.940001f, 5.5500002f, 16.940001f, 6.3299999f, 18.379999f, 6.5f), + MINOTAUR(6.6300001f, 15.95f, 4.2199998f, 15.95f, 6.3299999f, 18.379999f, 6.5f); + + private float walkStandard; + private float walkCombat; + private float runStandard; + private float runCombat; + private float swim; + private float flyRun; + private float flyWalk; + + RunSpeed(float walkStandard, float runStandard, float walkCombat, float runCombat, float flyWalk, float flyRun, float swim) { + this.walkStandard = walkStandard; + this.walkCombat = walkCombat; + this.runStandard = runStandard; + this.runCombat = runCombat; + this.swim = swim; + this.flyRun = flyRun; + this.flyWalk = flyWalk; + } + + + public float getWalkStandard() { + return walkStandard; + } + + public float getWalkCombat() { + return walkCombat; + } + + public float getRunStandard() { + return runStandard; + } + + public float getRunCombat() { + return runCombat; + } + + public float getFlyRun() { + return flyRun; + } + + + public float getFlyWalk() { + return flyWalk; + } + + } + + public enum FriendListType { + + VIEWHERALDRY(1), + ADDHERALDRY(4), + REMOVEHERALDRY(6), + DEALTHS(7), + KILLS(9), + VIEWCONDEMN(11), + ADDCONDEMN(14), + REMOVECONDEMN(15), + TOGGLEACTIVE(17), + REVERSEKOS(19), + VIEWFRIENDS(25), + TOITEM(23), + ADDFRIEND(28), + REMOVEFRIEND(30); + + private final int listType; + + FriendListType(int listType) { + this.listType = listType; + } + + public static FriendListType getListTypeByID(int listType) { + + FriendListType outType = null; + + for (FriendListType friendListType : FriendListType.values()) { + if (friendListType.listType == listType) + outType = friendListType; + } + return outType; + } + + } + + public enum DispatchChannel { + PRIMARY(0), + SECONDARY(1); + + private final int channelID; + + DispatchChannel(int channelID) { + this.channelID = channelID; + } + + public int getChannelID() { + return this.channelID; + } + + } + + public enum PvpHistoryType { + KILLS, + DEATHS; + } + + public enum ChatMessageType { + ERROR, + INFO, + MOTD; + } + + public enum DataRecordType { + PVP, + CHARACTER, + BANE, + GUILD, + CITY, + ZONE, + REALM, + MINE; + } + + public enum RecordEventType { + CREATE, // Shared with city/guild + DISBAND, + DESTROY, // City events + CAPTURE, + TRANSFER, + PENDING, + DEFEND, + LOST; // Realm event + } + + public enum CharterType { + FEUDAL(-600065291, 5060000), + MERCANTILE(-15978914, 5060400), + BELLIGERENT(762228431, 5060800); + + private int charterID; + private int meshID; + + CharterType(int charterID, int meshID) { + this.charterID = charterID; + this.meshID = meshID; + } + + public static CharterType getCharterTypeByID(int charterID) { + CharterType outType = null; + + for (CharterType charterType : CharterType.values()) { + if (charterType.charterID == charterID) + outType = charterType; + } + return outType; + } + + public int getMeshID() { + return meshID; + } + } + + + public enum ChatChannelType { + SYSTEM(1), + FLASH(2), + COMMANDER(3), + NATION(5), + LEADER(6), + SHOUT(7), + INFO(10), + GUILD(12), + INNERCOUNCIL(13), + GROUP(14), + CITY(15), + SAY(16), + EMOTE(17), + TELL(19), + COMBAT(20); + + private final int channelID; + + ChatChannelType(int channelID) { + this.channelID = channelID; + } + + public int getChannelID() { + return this.channelID; + } + } + + public enum OwnerType { + Npc, + PlayerCharacter, + Account, + Mob; + } + + public enum SiegePhase { + ERRANT, + CHALLENGE, + STANDOFF, + WAR, + CEASEFIRE; + } + + public enum SiegeResult { + PENDING, + DEFEND, + DESTROY, + CAPTURE; + } + + public enum TaxType { + PROFIT, + WEEKLY, + NONE; + + } + + public enum Ruins { + + ESTRAGOTH(569), + KARFELL(570), + MORELAN(571), + REGARS(572), + HALLOS(573), + WESTERMORE(574), + EYWAN(575), + CAER(576); + + private final int zoneUUID; + + Ruins(int uuid) { + this.zoneUUID = uuid; + } + + public static Ruins getRandomRuin() { + + Ruins ruins; + + ruins = Ruins.values()[ThreadLocalRandom.current() + .nextInt(Ruins.values().length)]; + + return ruins; + } + + public Vector3fImmutable getLocation() { + + Zone ruinZone; + Vector3fImmutable spawnLocation; + + // Send to SDR if so configured + + if (ConfigManager.MB_USE_RUINS.getValue().equalsIgnoreCase("true")) { + ruinZone = ZoneManager.getZoneByUUID(this.zoneUUID); + spawnLocation = Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc(), 30); + } else { + ruinZone = ZoneManager.getZoneByName("sea dog's rest"); + + // 14001 does not have a banestone to bind at + + if (ruinZone.getLoadNum() == 14001) + spawnLocation = Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc(), 30); + else + spawnLocation = Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc() + .add(new Vector3fImmutable(-196.016f, 2.812f, 203.621f)), 30); + } + + + return spawnLocation; + } + + } + + public enum Guards { + + HumanArcher(13.97f, 13.97f, 6.19f, 4.2199998f, 18.38f, 6.33f, 6.5f), + HumanGuard(13.97f, 13.97f, 6.19f, 4.2199998f, 18.38f, 6.33f, 6.5f), + HumanMage(13.97f, 13.97f, 6.19f, 4.2199998f, 18.38f, 6.33f, 6.5f), + UndeadArcher(14.67f, 14.67f, 6.5f, 4.44f, 18.38f, 6.33f, 6.5f), + UndeadGuard(14.67f, 14.67f, 6.5f, 4.44f, 18.38f, 6.33f, 6.5f), + UndeadMage(14.67f, 14.67f, 6.5f, 4.44f, 18.38f, 6.33f, 6.5f); + + private final float runSpeed; + private final float runCombatSpeed; + private final float walkSpeed; + private final float walkCombatSpeed; + private final float fly; + private final float flyWalk; + private final float swim; + + Guards(float runSpeed, float runCombatSpeed, float walkSpeed, float walkCombatSpeed, float fly, float flyWalk, float swim) { + this.runSpeed = runSpeed; + this.runCombatSpeed = runCombatSpeed; + this.walkSpeed = walkSpeed; + this.walkCombatSpeed = walkCombatSpeed; + this.fly = fly; + this.flyWalk = flyWalk; + this.swim = swim; + } + + public float getRunSpeed() { + return runSpeed; + } + + public float getRunCombatSpeed() { + return runCombatSpeed; + } + + public float getWalkSpeed() { + return walkSpeed; + } + + public float getWalkCombatSpeed() { + return walkCombatSpeed; + } + + public float getFly() { + return fly; + } + + public float getSwim() { + return swim; + } + + public float getFlyWalk() { + return flyWalk; + } + } + + public enum PortalType { + + EARTH(6f, 19.5f, 128), + AIR(-6f, 19.5f, 256), + FIRE(15f, 7.5f, 512), + WATER(-15f, 8.5f, 1024), + SPIRIT(0, 10.5f, 2048), + CHAOS(22f, 3.5f, 8192), + OBLIV(0f, 42f, 16384), + MERCHANT(-22f, 4.5f, 4096), + FORBID(0.0f, 0.0f, 0); + + public final Vector2f offset; + public final int effectFlag; + + PortalType(float offsetX, float offsetY, int effectFlag) { + + this.offset = new Vector2f(offsetX, offsetY); + this.effectFlag = effectFlag; + + } + } + + + // Enum for ItemBase flags + + public enum ItemType { + DECORATION(0), + WEAPON(1), + ARMOR(2), + HAIR(3), + GOLD(4), + RUNE(5), + SCROLL(5), + BOOK(6), + COMMANDROD(7), + POTION(8), + TEARS(8), + KEY(9), + GUILDCHARTER(10), + JEWELRY(13), + WINE(16), + ALEJUG(17), + DEED(19), + CONTRACT(20), + PET(21), + FURNITURE(25), + BEDROLL(26), + FARMABLE(27), + WATERBUCKET(30), + GIFT(31), + OFFERING(33), + RESOURCE(34), + REALMCHARTER(35); + + private final static HashMap _typeLookup = new HashMap<>(); + private final int _value; + + ItemType(int value) { + this._value = value; + } + + public static ItemType getByValue(int value) { + + ItemType outType = ItemType.DECORATION; + + if (_typeLookup.isEmpty()) { + + for (ItemType itemType : ItemType.values()) { + _typeLookup.put(itemType._value, itemType); + } + } + + if (_typeLookup.containsKey(value)) + outType = _typeLookup.get(value); + + return outType; + } + + /** + * @return the _value + */ + public int getValue() { + return _value; + } + + } + // Enum to derive effects for active spires from blueprintUUID + + public enum SpireType { + + WATCHFUL(1800100, (1 << 23), -1139520957), + GROUNDING(1800400, (1 << 24), -1733819072), + BINDING(1800700, (1 << 25), -1971545187), + WARDING(1801000, (1 << 26), 2122002462), + GUILEFUL(1801300, (1 << 27), -1378972677), + BALEFUL(1801600, -1, 1323012132), + ARCANE(1801900, (1 << 30), 1323888676), + WOUNDING(1802200, (1 << 10), 1357392095), + WEARYING(1802500, (1 << 10), 1350838495), + CONFUSING(1802800, (1 << 10), 1358702815), + CHILLING(1803100, (1 << 1), 1332155165), + SEARING(1803400, (1 << 2), -1401744610), + THUNDERING(1803700, (1 << 3), -443544829), + UNHOLY(1804000, (1 << 4), 1330320167), + BEFUDDLING(1804300, (1 << 5), 1489317547), + WRATHFUL(1804600, (1 << 6), 165160210), + SPITEFUL(1804900, (1 << 7), 1238906779), + ENFEEBLING(1805200, (1 << 8), -908578401), + CONFOUNDING(1805500, (1 << 9), 165165842), + DISTRACTING(1805800, (1 << 10), 1238906697), + WOLFPACK(1806100, (1 << 4), 416932375); + + private final int blueprintUUID; + private final int effectFlag; + private final int token; + + SpireType(int blueprint, int flag, int token) { + this.blueprintUUID = blueprint; + this.effectFlag = flag; + this.token = token; + } + + public static SpireType getByBlueprintUUID(int uuid) { + + SpireType outType = SpireType.GROUNDING; + + for (SpireType spireType : SpireType.values()) { + + if (spireType.blueprintUUID == uuid) { + outType = spireType; + return outType; + } + + } + + return outType; + } + + public int getBlueprintUUID() { + return blueprintUUID; + } + + public int getEffectFlag() { + return effectFlag; + } + + public int getToken() { + return token; + } + + public EffectsBase getEffectBase() { + return PowersManager.getEffectByToken(token); + } + + } + + public enum TransactionType { + MAINTENANCE(43), + WITHDRAWL(80), + DEPOSIT(82), + MINE(81), + MIGRATION(83), + PLAYERREWARD(84), + TAXRESOURCE(85), + TAXRESOURCEDEPOSIT(86); + + private final int ID; + + TransactionType(int ID) { + this.ID = ID; + } + + public int getID() { + return ID; + } + } + + public enum TargetColor { + + White, + Green, + Cyan, + Blue, + Yellow, + Orange, + Red; + + public static TargetColor getCon(AbstractCharacter source, + AbstractCharacter target) { + return getCon(source.getLevel(), target.getLevel()); + } + + public static TargetColor getCon(short sourceLevel, short targetLevel) { + if (targetLevel > (sourceLevel + 2)) + return Red; + else if (targetLevel == (sourceLevel + 2)) + return Orange; + else if (targetLevel == (sourceLevel + 1)) + return Yellow; + + short lowestBlue = (short) (sourceLevel - (((sourceLevel / 5)) + 2)); + + if (lowestBlue <= targetLevel) + return Blue; + else if (lowestBlue - 1 <= targetLevel) + return Cyan; + else if (lowestBlue - 2 <= targetLevel) + return Green; + return White; + } + } + + public enum DamageType { + None, + Crush, + Slash, + Siege, + Pierce, + Magic, + Bleed, + Poison, + Mental, + Holy, + Unholy, + Lightning, + Fire, + Cold, + Healing, + Acid, + Disease, + Unknown, + // these added for immunities + Attack, + Powers, + Combat, + Spires, + Snare, + Stun, + Blind, + Root, + Fear, + Charm, + PowerBlock, + DeBuff, + Powerblock, + Steel, + Drain; + + public static DamageType GetDamageType(String modName) { + DamageType damageType; + if (modName.isEmpty()) + return DamageType.None; + + try { + damageType = DamageType.valueOf(modName.replace(",", "")); + } catch (Exception e) { + Logger.error(e); + return DamageType.None; + } + return damageType; + } + } + + + public enum SourceType { + None, + Abjuration, + Acid, + AntiSiege, + Archery, + Axe, + Bardsong, + Beastcraft, + Benediction, + BladeWeaving, + Bleed, + Blind, + Block, + Bloodcraft, + Bow, + Buff, + Channeling, + Charm, + Cold, + COLD, + Constitution, + Corruption, + Crossbow, + Crush, + Dagger, + DaggerMastery, + DeBuff, + Dexterity, + Disease, + Dodge, + Dragon, + Drain, + Earth, + Effect, + Exorcism, + Fear, + Fire, + FIRE, + Fly, + Giant, + GreatAxeMastery, + GreatSwordMastery, + Hammer, + Heal, + Healing, + Holy, + HOLY, + ImmuneToAttack, + ImmuneToPowers, + Intelligence, + Invisible, + Lightning, + LIGHTNING, + Liturgy, + Magic, + MAGIC, + Mental, + MENTAL, + NatureLore, + Necromancy, + Parry, + Pierce, + Poison, + POISON, + PoleArm, + Powerblock, + Rat, + ResistDeBuff, + Restoration, + Root, + Shadowmastery, + Siege, + Slash, + Snare, + Sorcery, + Spear, + SpearMastery, + Spirit, + Staff, + Stormcalling, + Strength, + Stun, + Summon, + Sword, + SwordMastery, + Thaumaturgy, + Theurgy, + Transform, + UnarmedCombat, + UnarmedCombatMastery, + Unholy, + UNHOLY, + Unknown, + Warding, + Warlockry, + WayoftheGaana, + WearArmorHeavy, + WearArmorLight, + WearArmorMedium, + Wereform, + Athletics, + AxeMastery, + Bargaining, + BladeMastery, + FlameCalling, + GreatHammerMastery, + HammerMastery, + Leadership, + PoleArmMastery, + Running, + StaffMastery, + Throwing, + Toughness, + WayoftheWolf, + WayoftheRat, + WayoftheBear, + Orthanatos, + SunDancing, + //Power categories. + AE, + AEDAMAGE, + BEHAVIOR, + BLESSING, + BOONCLASS, + BOONRACE, + BREAKFLY, + BUFF, + CHANT, + DAMAGE, + DEBUFF, + DISPEL, + FLIGHT, + GROUPBUFF, + GROUPHEAL, + HEAL, + INVIS, + MOVE, + RECALL, + SPECIAL, + SPIREDISABLE, + SPIREPROOFTELEPORT, + STANCE, + STUN, + SUMMON, + TELEPORT, + THIEF, + TRACK, + TRANSFORM, + VAMPDRAIN, + WEAPON, + Wizardry; + + public static SourceType GetSourceType(String modName) { + SourceType returnMod; + if (modName.isEmpty()) + return SourceType.None; + + try { + returnMod = SourceType.valueOf(modName.replace(",", "")); + } catch (Exception e) { + Logger.error(modName); + Logger.error(e); + return SourceType.None; + } + return returnMod; + } + } + + public enum EffectSourceType { + None, + AttackSpeedBuff, + Bleeding, + Blind, + Buff, + Chant, + Charm, + Cold, + Combat, + ConstitutionBuff, + Crush, + DamageShield, + DeathShroud, + DeBuff, + Disease, + Drain, + Earth, + Effect, + Fear, + Fire, + Flight, + Fortitude, + Heal, + Holy, + Invisibility, + Invulnerability, + Lightning, + Magic, + Mental, + Multielement, + PetBuff, + Pierce, + Poison, + Powerblock, + RecoveryManaBuff, + ResistDeBuff, + Root, + Siege, + SiegeBuff, + SiegeDamage, + Silence, + Slash, + Snare, + Stance, + Stun, + Summon, + Transform, + Unholy, + Wereform, + WereformATRBuff, + WereformConBuff, + WereformDexBuff, + WereformHPRecBuff, + WereformMoveBuff, + WereformPhysResBuff, + WereformSPRecBuff, + WereformStrBuff; + + public static EffectSourceType GetEffectSourceType(String modName) { + EffectSourceType returnMod; + if (modName.isEmpty()) + return EffectSourceType.None; + + try { + returnMod = EffectSourceType.valueOf(modName.replace(",", "")); + } catch (Exception e) { + Logger.error(e); + return EffectSourceType.None; + } + return returnMod; + } + } + + public enum StackType { + None, + AggRangeDeBuff, + ArcheryPrecisionBuff, + AttackDebuff, + AttackSpeedBuff, + AttackSpeedDeBuff, + AttackValueBuff, + AttackValueDebuff, + AttrCONBuff, + AttrCONDebuff, + AttrDEXBuff, + AttrINTBuff, + AttrSPRBuff, + AttrSTRBuff, + Bleeding, + Blindness, + BluntResistanceDebuff, + BMHealing, + Charm, + ClassBoon, + Confusion, + DamageAbsorber, + DamageDebuff, + DamageModifierBuff, + DamageShield, + DeathShroud, + DefenseBuff, + DefenseBuffGroup, + DefenseDebuff, + DetectInvis, + DrainImmunity, + ElementalDeBuff, + EnchantWeapon, + Fear, + Flight, + Frenzy, + GroupHeal, + HealingBuff, + HealOverTime, + HealResBuff, + HealthPotion, + IgnoreStack, + Invisible, + ManaPotion, + MangonelFire, + MeleeDamageDeBuff, + MeleeDeBuff, + MoveBuff, + MoveDebuff, + NoFear, + NoPassiveDefense, + NoPowerBlock, + NoPowerInhibitor, + NoRecall, + NoSnare, + NoStun, + NoTrack, + PassiveDefense, + PersAttrSPRBuff, + PetBuff, + PierceResistanceDebuff, + PoisonBuchinine, + PoisonGalpa, + PoisonGorgonsVenom, + PoisonMagusbane, + PoisonPellegorn, + PowerBlock, + PowerCostBuff, + PowerDamageModifierBuff, + PowerInhibitor, + PrecisionBuff, + Protection, + RaceBoon, + RecoveryHealthBuff, + RecoveryHealthDeBuff, + RecoveryManaBuff, + RecoveryManaDeBuff, + RecoveryStaminaBuff, + RecoveryStaminaDeBuff, + ResistanceBuff, + ResistanceDeBuff, + ResistanceDebuff, + Root, + SafeMode, + SelfOneAttrBuff, + SelfThreeAttrBuff, + SelfTwoAttrBuff, + SiegeDebuff, + SiegeWeaponBuff, + Silence, + SkillDebuff, + SlashResistanceDebuff, + Snare, + StackableAttrCONBuff, + StackableAttrDEXBuff, + StackableAttrSTRBuff, + StackableDefenseBuff, + StackableRecoveryHealthBuff, + StackableRecoveryStaminaBuff, + StaminaPotion, + StanceA, + StanceB, + Stun, + Track, + Transform, + WeaponMove; + + public static StackType GetStackType(String modName) { + StackType stackType; + if (modName.isEmpty()) + return StackType.None; + + try { + stackType = StackType.valueOf(modName.replace(",", "")); + } catch (Exception e) { + Logger.error(modName); + Logger.error(e); + return StackType.None; + } + return stackType; + } + } + + public enum ModType { + None, + AdjustAboveDmgCap, + Ambidexterity, + AnimOverride, + ArmorPiercing, + AttackDelay, + Attr, + BlackMantle, + BladeTrails, + Block, + BlockedPowerType, + CannotAttack, + CannotCast, + CannotMove, + CannotTrack, + Charmed, + ConstrainedAmbidexterity, + DamageCap, + DamageShield, + DCV, + Dodge, + DR, + Durability, + ExclusiveDamageCap, + Fade, + Fly, + Health, + HealthFull, + HealthRecoverRate, + IgnoreDamageCap, + IgnorePassiveDefense, + ImmuneTo, + ImmuneToAttack, + ImmuneToPowers, + Invisible, + ItemName, + Mana, + ManaFull, + ManaRecoverRate, + MaxDamage, + MeleeDamageModifier, + MinDamage, + NoMod, + OCV, + Parry, + PassiveDefense, + PowerCost, + PowerCostHealth, + PowerDamageModifier, + ProtectionFrom, + Resistance, + ScaleHeight, + ScaleWidth, + ScanRange, + SeeInvisible, + Silenced, + Skill, + Slay, + Speed, + SpireBlock, + Stamina, + StaminaFull, + StaminaRecoverRate, + Stunned, + Value, + WeaponProc, + WeaponRange, + WeaponSpeed; + + public static ModType GetModType(String modName) { + ModType modType; + if (modName.isEmpty()) + return ModType.None; + + try { + modType = ModType.valueOf(modName.replace(",", "")); + } catch (Exception e) { + Logger.error(e); + return ModType.None; + } + return modType; + } + } + + public enum MovementState { + + IDLE, + SITTING, + RUNNING, + FLYING, + SWIMMING; + } + + public enum DoorState { + + OPEN, + CLOSED, + LOCKED, + UNLOCKED; + } + + // Used with stored procedure GET_UID_ENUM() for + // type tests against objects not yet loaded into the game. + public enum DbObjectType { + + INVALID, + ACCOUNT, + BUILDING, + CHARACTER, + CITY, + CONTAINER, + GUILD, + ITEM, + MINE, + MOB, + NPC, + SHRINE, + WORLDSERVER, + ZONE, + WAREHOUSE; + } + + ; + + /** + * Enumeration of Building Protection Status stored in the database as a + * mysql enumfield. WARNING: This enumeration is fragile. Do not rename. Do + * not reorder. + */ + public enum ProtectionState { + + NONE, + PROTECTED, + UNDERSIEGE, + CEASEFIRE, + CONTRACT, + DESTROYED, + PENDING, + NPC; + } + + ; + + public enum CharacterSkills { + + Archery((1L << 1), -529201545, 20), + Athletics((1L << 2), -327713877, 15), + AxeMastery((1L << 3), 1103042709, 20), + Axe((1L << 4), 73505, 1), + Bardsong((1L << 5), 454246953, 10), + Bargaining((1L << 6), 372927577, 10), + Beastcraft((1L << 7), 56772766, 10), + Benediction((1L << 8), 1464998706, 1), + BladeMastery((1L << 9), -59908956, 20), + BladeWeaving((1L << 10), -1839362429, 20), + Block((1L << 11), 76592546, 3), + Bow((1L << 12), 87490, 1), + Channeling((1L << 13), -1899060872, 20), + Crossbow((1L << 14), 1092138184, 1), + DaggerMastery((1L << 15), -1549224741, 20), + Dagger((1L << 16), -1603103740, 1), + Dodge((1L << 17), 74619332, 5), + FlameCalling((1L << 18), -1839578206, 20), + GreatAxeMastery((1L << 19), 1427003458, 20), + GreatHammerMastery((1L << 20), -309659310, 20), + GreatSwordMastery((1L << 21), 2054956946, 20), + HammerMastery((1L << 22), -1548903209, 20), + Hammer((1L << 23), -1602765816, 1), + Leadership((1L << 24), 1618560984, 20), + Liturgy((1L << 25), -888415974, 10), + NatureLore((1L << 26), -1911171474, 10), + Parry((1L << 27), 95961104, 5), + PoleArmMastery((1L << 28), -1432303709, 20), + PoleArm((1L << 29), -1037845588, 1), + Restoration((1L << 30), -504697054, 1), + Running((1L << 31), 1488335491, 10), + Shadowmastery((1L << 32), 1389222957, 10), + Sorcery((1L << 33), -529481275, 1), + SpearMastery((1L << 34), -48279755, 20), + Spear((1L << 35), 83992115, 1), + StaffMastery((1L << 36), -61022283, 20), + Staff((1L << 37), 71438003, 1), + Stormcalling((1L << 38), -532064061, 10), + SwordMastery((1L << 39), -59316267, 20), + Sword((1L << 40), 73938643, 1), + Thaumaturgy((1L << 41), -2020131447, 10), + Theurgy((1L << 42), -888431326, 10), + Throwing((1L << 43), 391562015, 20), + Toughness((1L << 44), -660435875, 10), + UnarmedCombatMastery((1L << 45), 1692733771, 20), + UnarmedCombat((1L << 46), -1094332856, 1), + Warding((1L << 47), 1488142342, 1), + Warlockry((1L << 48), 1121393557, 10), + WayoftheGaana((1L << 49), -1954832975, 10), + WearArmorHeavy((1L << 50), 1112121635, 15), + WearArmorLight((1L << 51), 38031547, 1), + WearArmorMedium((1L << 52), 468015203, 5), + Wizardry((1L << 53), 218227659, 10), + Corruption((1L << 54), -1519268706, 10), + Abjuration((1L << 55), -2029900484, 10), + WayoftheWolf((1L << 56), 1668913067, 20), + WayoftheRat((1L << 57), -2114353637, 20), + WayoftheBear((1L << 58), -906390863, 20), + Orthanatos((1L << 59), -666929185, 20), + Bloodcraft((1L << 60), 40661438, 10), + Exorcism((1L << 61), 1444427097, 10), + Necromancy((1L << 62), -556571154, 10), + SunDancing((1L << 63), 22329752, 20); + + private long flag; + private int token; + private int reqLvl; + + CharacterSkills(long flag, int token, int reqLvl) { + this.flag = flag; + this.token = token; + this.reqLvl = reqLvl; + } + + public static CharacterSkills GetCharacterSkillByToken(int token) { + for (CharacterSkills skill : CharacterSkills.values()) { + if (skill.token == token) + return skill; + } + + Logger.info("Returned No Skill for token " + token + ". Defaulting to Axe"); + return CharacterSkills.Axe; + } + + public long getFlag() { + return flag; + } + + public void setFlag(long flag) { + this.flag = flag; + } + + public int getReqLvl() { + return this.reqLvl; + } + + public int getToken() { + return token; + } + + public void setToken(int token) { + this.token = token; + } + } + + ; + + public enum GuildHistoryType { + JOIN(1), + LEAVE(4), + BANISHED(3), + CREATE(7), + DISBAND(5); + private final int type; + + GuildHistoryType(int type) { + this.type = type; + } + + public int getType() { + return type; + } + } + + public enum SexType { + NONE, + MALE, + FEMALE; + } + + public enum ClassType { + FIGHTER, + HEALER, + ROGUE, + MAGE; + } + + public enum PromoteType { + Assassin(SexType.NONE), + Barbarian(SexType.NONE), + Bard(SexType.NONE), + Channeler(SexType.NONE), + Confessor(SexType.NONE), + Crusader(SexType.NONE), + Doomsayer(SexType.NONE), + Druid(SexType.NONE), + Fury(SexType.FEMALE), + Huntress(SexType.FEMALE), + Prelate(SexType.NONE), + Priest(SexType.NONE), + Ranger(SexType.NONE), + Scout(SexType.NONE), + Sentinel(SexType.NONE), + Templar(SexType.NONE), + Thief(SexType.NONE), + Warlock(SexType.MALE), + Warrior(SexType.NONE), + Wizard(SexType.NONE), + Nightstalker(SexType.NONE), + Necromancer(SexType.NONE), + ; + + private SexType sexRestriction; + + PromoteType(SexType sexRestriction) { + this.sexRestriction = sexRestriction; + } + + public SexType getSexRestriction() { + return sexRestriction; + } + } + + public enum ShrineType { + + Aelfborn(1701900, -75506007, true), + Aracoix(1703100, -563708986, true), + Centaur(1704000, 521645243, true), + Dwarf(1708500, -2000467257, true), + Elf(1703400, 1254603001, true), + HalfGiant(1709100, 349844468, true), + Human(1702200, 281172391, true), + Irekei(1702800, -764988442, true), + Minotaur(1704600, 549787579, true), + Nephilim(1701000, -655183799, true), + Shade(1700100, 1724071104, true), + Assassin(1700400, 1989015892, false), + Barbarian(1708800, 9157124, false), + Bard(1704300, 80190554, false), + Channeler(1702500, 5658278, false), + Confessor(1707600, 1871658719, false), + Crusader(1706700, -187454619, false), + Doomsayer(1700700, -993659433, false), + Druid(1701600, -926740122, false), + Fury(1705500, 214401375, false), + Huntress(1704900, 970312892, false), + Prelate(1707000, -225200922, false), + Priest(1705200, -535691898, false), + Ranger(1701300, 604716986, false), + Scout(1706100, -1497297486, false), + Sentinel(1707300, -184898375, false), + Templar(1707900, 826673315, false), + Thief(1708200, 1757633920, false), + Warlock(1706400, 1003385946, false), + Warrior(1703700, 931048026, false), + Wizard(1705800, 777115928, false), + Nightstalker(1709400, 373174890, false), + Necromancer(1709700, -319294505, false), + Vampire(1710000, 1049274530, true); + + private final int blueprintUUID; + private final int powerToken; + private final ArrayList shrines = new ArrayList<>(); + private final boolean isRace; + + ShrineType(int blueprintUUID, int powerToken, boolean isRace) { + this.blueprintUUID = blueprintUUID; + this.powerToken = powerToken; + this.isRace = isRace; + + } + + public int getBlueprintUUID() { + return blueprintUUID; + } + + public int getPowerToken() { + return powerToken; + } + + public ArrayList getShrinesCopy() { + ArrayList copyShrines = new ArrayList<>(); + copyShrines.addAll(shrines); + Collections.sort(copyShrines); + return copyShrines; + } + + public final void addShrineToServerList(Shrine shrine) { + synchronized (shrines) { + shrines.add(shrine); + } + } + + public final void RemoveShrineFromServerList(Shrine shrine) { + synchronized (shrines) { + shrines.remove(shrine); + } + } + + public boolean isRace() { + return isRace; + } + } + + public enum GuildState { + + Errant(0), + Sworn(4), + Protectorate(6), + Petitioner(2), + Province(8), + Nation(5), + Sovereign(7); + + private final int stateID; + + GuildState(int stateID) { + this.stateID = stateID; + } + + public int getStateID() { + return stateID; + } + + } + + + // Building group enumeration. + // This is used to drive linear equations to calculate + // structure hp, ranking times and such from within + // the BuildingBlueprint class. + // + // It is also used as a bitvector flag in the npc + // building slot mechanics. + + public enum BuildingGroup implements EnumBitSetHelper { + NONE(0, 0), + TOL(64f, 64f), + BARRACK(32f, 64f), + CHURCH(64f, 64f), + FORGE(32f, 64f), + SPIRE(16f, 16f), + GENERICNOUPGRADE(16f, 16f), + WALLSTRAIGHT(16f, 64), + WALLCORNER(64f, 64f), + SMALLGATE(64f, 64), + ARTYTOWER(64f, 64), + SIEGETENT(32f, 32f), + BANESTONE(16f, 16f), + MINE(16f, 16f), + WAREHOUSE(32f, 32f), + SHRINE(16f, 16f), + RUNEGATE(64f, 64f), + AMAZONHALL(64f, 64f), + CATHEDRAL(64f, 64f), + GREATHALL(64f, 64f), + KEEP(64f, 64f), + THIEFHALL(64f, 24f), + TEMPLEHALL(64f, 64f), + WIZARDHALL(64f, 64f), + ELVENHALL(64f, 64f), + ELVENSANCTUM(64f, 64f), + IREKEIHALL(64f, 64f), + FORESTHALL(64f, 64f), + MAGICSHOP(32f, 32f), + BULWARK(32f, 32f), + SHACK(16f, 16f), + INN(64f, 32f), + TAILOR(32f, 32f), + VILLA(64f, 32f), + ESTATE(64f, 64f), + FORTRESS(64f, 64f), + CITADEL(64f, 64f), + WALLSTRAIGHTTOWER(32F, 64), + WALLSTAIRS(64, 64); + + private final Vector2f extents; + + BuildingGroup(float extentX, float extentY) { + this.extents = new Vector2f(extentX, extentY); + } + + public Vector2f getExtents() { + return extents; + } + + } + + public enum UpdateType { + ALL, + MOVEMENT, + REGEN, + FLIGHT, + LOCATION, + MOVEMENTSTATE; + } + + public enum ServerType { + WORLDSERVER, + LOGINSERVER, + NONE; + } + + public enum ChatChannel implements EnumBitSetHelper { + System, + Announce, + Unknown, + Commander, + Address, + Nation, + Leader, + Shout, + Siege, + Territory, + Info, + CSR, + Guild, + InnerCouncil, + Group, + City, + Say, + Emote, + Social, + Tell, + Combat, + Powers, + Snoop, + Debug, + Global, + Trade, + PVP, + Mine, + Alert, + Assassin, + Barbarian, + Bard, + Channeler, + Confessor, + Crusader, + Doomsayer, + Druid, + Fury, + Huntress, + Necromancer, + Nightstalker, + Prelate, + Priest, + Ranger, + Scout, + Sentinel, + Templar, + Thief, + Warlock, + Warrior, + Wizard; + + } + + public enum AllianceType { + RecommendedAlly, + RecommendedEnemy, + Ally, + Enemy; + } + + public enum FriendStatus { + Available, + Away, + Busy; + } + + public enum ProfitType { + + + BuyNormal("buy_normal"), + BuyGuild("buy_guild"), + BuyNation("buy_nation"), + SellNormal("sell_normal"), + SellGuild("sell_guild"), + SellNation("sell_nation"); + + public String dbField; + + private ProfitType(String dbField) { + this.dbField = dbField; + } + } + + public enum GameObjectType { + + /* + * These will be used as the 4 high bytes in the application protocol's + * long CompositeID field and when tracking an AbstractGameObject's type + * from within the code. The low 4 bytes will be used as the Object's + * UUID + */ + unknown, + Account, + AccountIP, + ActiveEffect, + ArmorBase, + BaseClass, + BeardStyle, + BlockedIP, + Building, + BuildingLocation, + BuildingModelBase, + CharacterPower, + CharacterPowers, + CharacterRune, + CharacterSkill, + City, + Contract, + Corpse, + CSSession, + EffectsResourceCosts, + EnchantmentBase, + GenericItemBase, + Group, + Guild, + GuildAllianceEnemy, + GuildBanish, + GuildCharacterKOS, + GuildGuildKOS, + GuildTableList, + HairStyle, + Item, + ItemContainer, + ItemEnchantment, + JewelryBase, + Kit, + MenuOption, + Mine, + Mob, + MobBase, + MobEquipment, + MobLoot, + MobType, + NPC, + NPCClassRune, + NPCClassRuneThree, + NPCClassRuneTwo, + NPCExtraRune, + NPCRaceRune, + NPCRune, + NPCShopkeeperRune, + NPCTrainerRune, + Nation, + Petition, + PlayerCharacter, + PlayerInfo, + PowerGrant, + PowerReq, + PowersBase, + PowersBaseAttribute, + PromotionClass, + Race, + RuneBase, + RuneBaseAttribute, + RuneBaseEffect, + SkillReq, + SkillsBase, + SkillsBaseAttribute, + MobileBooty, + StrongBox, + Trigger, + ValidRaceBeardStyle, + ValidRaceClassCombo, + ValidRaceHairStyle, + VendorDialog, + Warehouse, + WeaponBase, + WorldServerInfo, + WorldServerInfoSnapshot, + Shrine, + Zone, + Transaction; + } + + public enum ContainerType { + BANK, + INVENTORY, + VAULT; + } + + ; + + public enum CompoundCurveType { + DefaultFlat(0), + DefaultSlope(1), + DefaultSlopeDown(-1), + SL0001Up(0.01), + SL0003Up(0.03), + SL0005Up(0.05), + SL0006Up(0.06), + SL0007Up(0.07), + SL0008Up(0.08), + SL0010Up(0.10), + SL0011Up(0.11), + SL0012Up(0.12), + SL0013Up(0.13), + SL0014Up(0.14), + SL00143U(0.143), + SL0015Up(0.15), + SL0016Up(0.16), + SL0019Up(0.19), + SL0020Up(0.20), + SL0021Up(0.21), + SL0022Up(0.22), + SL0023Up(0.23), + SL0024Up(0.24), + SL0025Up(0.25), + SL0026Up(0.26), + SL0028Up(0.28), + SL0030Up(0.30), + SL0031Up(0.31), + SL0032Up(0.32), + SL0033Up(0.33), + SL0034Up(0.34), + SL0035Up(0.35), + SL0037Up(0.37), + SL0038Up(0.38), + SL0039Up(0.39), + SL0040Up(0.40), + SL0041Up(0.41), + SL0042Up(0.42), + SL0043Up(0.43), + SL0044Up(0.44), + SL0045Up(0.45), + SL0046Up(0.46), + SL0047Up(0.47), + SL0048Up(0.48), + SL0050Up(0.50), + SL0051Up(0.51), + SL0053Up(0.53), + SL0054Up(0.54), + SL0055Up(0.55), + SL0056Up(0.56), + SL0057Up(0.57), + SL0058Up(0.58), + SL0060Up(0.60), + SL0061Up(0.61), + SL0063Up(0.63), + SL0064Up(0.64), + SL0065Up(0.65), + SL0066Up(0.66), + SL0067Up(0.67), + SL0068Up(0.68), + SL0069Up(0.69), + SL0070Up(0.70), + SL0071Up(0.71), + SL0073Up(0.73), + SL0074Up(0.74), + SL0075Up(0.75), + SL0076Up(0.76), + SL0077Up(0.77), + SL0079Up(0.79), + SL0080Up(0.80), + SL0081Up(0.81), + SL0082Up(0.82), + SL0083Up(0.83), + SL0084Up(0.84), + SL0085Up(0.85), + SL0087Up(0.87), + SL0088Up(0.88), + SL0089Up(0.89), + SL0090Up(0.90), + SL0092Up(0.92), + SL0098Up(0.98), + SL0100Up(1.00), + SL0106Up(1.06), + SL0109Up(1.09), + SL0112Up(1.12), + SL0113Up(1.13), + SL0115Up(1.15), + SL0116Up(1.16), + SL0122Up(1.22), + SL0123Up(1.23), + SL0125Up(1.25), + SL0128Up(1.28), + SL0130Up(1.30), + SL0135Up(1.35), + SL0140Up(1.40), + SL0143Up(1.43), + SL0145Up(1.45), + SL0150Up(1.50), + SL0154Up(1.54), + SL0163Up(1.63), + SL0166Up(1.66), + SL0175Up(1.75), + SL0188Up(1.88), + SL0190Up(1.90), + SL0200Up(2.00), + SL0222Up(2.22), + SL0225Up(2.25), + SL0235Up(2.35), + SL0238Up(2.38), + SL0250Up(2.50), + SL0260Up(2.60), + SL0263Up(2.63), + SL0275Up(2.75), + SL0280Up(2.80), + SL0300Up(3.00), + SL0308Up(3.08), + SL0312Up(3.12), + SL0350Up(3.50), + SL0357Up(3.57), + SL0360Up(3.60), + SL0375Up(3.75), + SL0380Up(3.80), + SL0385Up(3.85), + SL0400Up(4.00), + SL0410Up(4.10), + SL0429Up(4.29), + SL0450Up(4.50), + SL0460Up(4.60), + SL0480Up(4.80), + SL0500Up(5.00), + SL0510Up(5.10), + SL0550Up(5.50), + SL0600Up(6.00), + SL0643Up(6.43), + SL0714Up(7.14), + SL0750Up(7.50), + SL0790Up(7.90), + SL0800Up(8.00), + SL0900Up(9.00), + SL1000Up(10.00), + SL1050Up(10.50), + SL1100Up(11.00), + SL1125Up(11.25), + SL1200Up(12.00), + SL1282Up(12.82), + SL1300Up(13.00), + SL1350Up(13.50), + SL1400Up(14.00), + SL1500Up(15.00), + SL1579Up(15.79), + SL2000Up(20.00), + SL2100Up(21.00), + SL2500Up(25.00), + SL2521Up(25.21), + SL3000Up(30.00), + SL4000Up(40.00), + SL5000Up(50.00), + SL6000Up(60.00), + SL7500Up(75.00), + SL8000Up(80.00), + SL12000Up(120.00), + SL14000Up(140.00), + SL30000Up(300.00), + SL66600Up(666.00), + SL71500Up(715.00), + SL00003Down(-0.003), + SL0001Down(-0.01), + SL0003Down(-0.03), + SL0004Down(-0.04), + SL0005Down(-0.05), + SL0006Down(-0.06), + SL0007Down(-0.07), + SL00075Down(-0.075), + SL0008Down(-0.08), + SL0009Down(-0.09), + SL0010Down(-0.10), + SL0011Down(-0.11), + SL0012Down(-0.12), + SL0013Down(-0.13), + SL00125Down(-0.125), + SL0014Down(-0.14), + SL0015Down(-0.15), + SL0016Down(-0.16), + SL0017Down(-0.17), + SL00175Down(-0.175), + SL0018Down(-0.18), + SL0019Down(-0.19), + SL0020Down(-0.20), + SL0023Down(-0.23), + SL0024Down(-0.24), + SL0025Down(-0.25), + SL0027Down(-0.27), + SL0028Down(-0.28), + SL0029Down(-0.29), + SL0030Down(-0.30), + SL0032Down(-0.32), + SL0033Down(-0.33), + SL0035Down(-0.35), + SL0038Down(-0.38), + SL0040Down(-0.40), + SL0044Down(-0.44), + SL0045Down(-0.45), + SL0050Down(-0.50), + SL0055Down(-0.55), + SL0060Down(-0.60), + SL0062Down(-0.62), + SL0063Down(-0.63), + SL0064Down(-0.64), + SL0066Down(-0.66), + SL0069Down(-0.69), + SL0071Down(-0.71), + SL0075Down(-0.75), + SL0077Down(-0.77), + SL0079Down(-0.79), + SL0080Down(-0.80), + SL0090Down(-0.90), + SL0100Down(-1.00), + SL0113Down(-1.13), + SL0120Down(-1.20), + SL0125Down(-1.25), + SL0128Down(-1.28), + SL0130Down(-1.30), + SL0135Down(-1.35), + SL0150Down(-1.50), + SL0175Down(-1.75), + SL0188Down(-1.88), + SL0200Down(-2.00), + SL0225Down(-2.25), + SL0250Down(-2.50), + SL0263Down(-2.63), + SL0300Down(-3.00), + SL0357Down(-3.57), + SL0385Down(-3.85), + SL0429Down(-4.29), + SL0450Down(-4.50), + SL0500Down(-5.00), + SL0550Down(-5.50), + SL0600Down(-6.00), + SL0643Down(-6.43), + SL0714Down(-7.14), + SL0750Down(-7.50), + SL0790Down(-7.90), + SL0800Down(-8.00), + SL1000Down(-10.00), + SL1050Down(-10.50), + SL1200Down(-12.00), + SL1350Down(-13.50), + SL1500Down(-15.00), + SL1579Down(-15.79), + SL2000Down(-20.00), + SL2400Down(-24.00), + SL2500Down(-25.00), + SL3000Down(-30.00), + SL4500Down(-45.00), + SL7500Down(-75.00), + SIVL0005(0.005), + SIVL0008(0.008), + SIVL0009(0.009), + SIVL0010(0.010), + SIVL0012(0.012), + SIVL0013(0.013), + SIVL0014(0.014), + SIVL0015(0.015), + SIVL0016(0.016), + SIVL0017(0.017), + SIVL0019(0.019), + SIVL0020(0.020), + SIVL0021(0.021), + SIVL0022(0.022), + SIVL0023(0.023), + SIVL0024(0.024), + SIVL0025(0.025), + SIVL0026(0.026), + SIVL0027(0.027), + SIVL0029(0.029), + SIVL0030(0.030), + SIVL0031(0.031), + SIVL0032(0.032), + SIVL0033(0.033), + SIVL0034(0.034), + SIVL0035(0.035), + SIVL0036(0.036), + SIVL0038(0.038), + SIVL0040(0.040), + SIVL0044(0.044), + SIVL0046(0.046), + SIVL0048(0.048), + SIVL0055(0.055), + SIVL0056(0.056), + SIVL0057(0.057), + SIVL0058(0.058), + SIVL0060(0.060), + SIVL0061(0.061), + SIVL0066(0.066), + SIVL0067(0.067), + SIVL0075(0.075), + SIVL0078(0.078), + SIVL0130(0.130), + SIVL0150(0.150), + SIVL0205(0.205), + SIVL0220(0.220), + SIVL0243(0.243), + SIVL0360(0.360); + + private final double value; + + private CompoundCurveType(double value) { + + this.value = value; + } + + public double getValue() { + return value; + } + } + + public enum PowerFailCondition { + + Attack, + AttackSwing, + Cast, + CastSpell, + EquipChange, + Logout, + Move, + NewCharm, + Sit, + TakeDamage, + TerritoryClaim, + UnEquip; + } + + public enum PowerSubType { + Amount, + Ramp, + UseAddFormula, + DamageType1, + DamageType2, + DamageType3, + Cancel; + } + + public enum PowerCategoryType { + NONE, + WEAPON, + BUFF, + DEBUFF, + SPECIAL, + DAMAGE, + DISPEL, + INVIS, + STUN, + TELEPORT, + HEAL, + VAMPDRAIN, + BLESSING, + BOONRACE, + BOONCLASS, + BEHAVIOR, + CHANT, + GROUPBUFF, + MOVE, + FLIGHT, + GROUPHEAL, + AEDAMAGE, + BREAKFLY, + AE, + TRANSFORM, + TRACK, + SUMMON, + STANCE, + RECALL, + SPIREPROOFTELEPORT, + SPIREDISABLE, + THIEF; + } + + public enum PowerTargetType { + + SELF, + PCMOBILE, + PET, + MOBILE, + PC, + WEAPON, + GUILDLEADER, + BUILDING, + GROUP, + ARMORWEAPONJEWELRY, + CORPSE, + JEWELRY, + WEAPONARMOR, + ARMOR, + ITEM; + } + + public enum objectMaskType { + PLAYER, + MOB, + PET, + CORPSE, + BUILDING, + UNDEAD, + BEAST, + HUMANOID, + NPC, + IAGENT, + DRAGON, + RAT, + SIEGE, + CITY, + ZONE; + + public static EnumSet AGGRO = EnumSet.of(PLAYER, PET); + public static EnumSet MOBILE = EnumSet.of(PLAYER, MOB, PET); + public static EnumSet STATIC = EnumSet.of(CORPSE, BUILDING, NPC); + + } + + public enum ItemContainerType { + NONE, + INVENTORY, + EQUIPPED, + BANK, + VAULT, + FORGE, + WAREHOUSE; + } + + public enum ItemSlotType implements EnumBitSetHelper { + RHELD, + LHELD, + HELM, + CHEST, + SLEEVES, + HANDS, + RRING, + LRING, + AMULET, + LEGS, + FEET, + CLOAK, + SHIN, + UPLEGS, + UPARM, + WINGS, + BEARD, + HAIR; + } + + public enum CityBoundsType { + + GRID(640), + ZONE(875), + PLACEMENT(876); + + public final float extents; + + CityBoundsType(float extents) { + this.extents = extents; + } + } + + public enum GuildType { + NONE("None", new String[][]{{"None"}}, new String[]{"Thearchy", "Common Rule", "Theocracy", "Republic Rule"}), + CATHEDRAL("Church of the All-Father", new String[][]{ + {"Acolyte", "Acolyte"}, + {"Catechist"}, + {"Deacon", "Deaconess"}, + {"Priest", "Priestess"}, + {"High Priest", "High Priestess"}, + {"Bishop", "Bishop"}, + {"Lord Cardinal", "Lady Cardinal"}, + {"Patriarch", "Matriarch"}}, + new String[]{"Thearchy", "Common Rule", "Theocracy", "Republic Rule"}), + MILITARY("Military", new String[][]{ + {"Recruit"}, + {"Footman"}, + {"Corporal"}, + {"Sergeant"}, + {"Lieutenant"}, + {"Captain"}, + {"General"}, + {"Lord Marshall", "Lady Marshall"}}, + new String[]{"Autocracy", "Common Rule", "Council Rule", "Militocracy"}), + TEMPLE("Temple of the Cleansing Flame", new String[][]{ + {"Aspirant"}, + {"Novice"}, + {"Initiate"}, + {"Inquisitor"}, + {"Jannisary"}, + {"Tribune"}, + {"Lictor"}, + {"Justiciar"}, + {"Pontifex", "Pontifectrix"}}, + new String[]{"Despot Rule", "Common Rule", "Protectorship", "Republic Rule"}), + BARBARIAN("Barbarian Clan", new String[][]{ + {"Barbarian"}, + {"Skald"}, + {"Raider"}, + {"Karl"}, + {"Jarl"}, + {"Chieftain"}, + {"Thane"}}, + new String[]{"Chiefdom", "Common Rule", "Council Rule", "Republic Rule"}), + RANGER("Ranger's Brotherhood", new String[][]{ + {"Yeoman"}, + {"Pathfinder"}, + {"Tracker"}, + {"Seeker"}, + {"Protector"}, + {"Guardian"}, + {"Lord Protector", "Lady Protector"}}, + new String[]{"Despot Rule", "Collectivism", "Council Rule", "Republic Rule"}), + AMAZON("Amazon Temple", new String[][]{ + {"Amazon Thrall", "Amazon"}, + {"Amazon Slave", "Amazon Warrior"}, + {"Amazon Servant", "Amazon Chieftess"}, + {"Amazon Consort", "Amazon Princess"}, + {"Amazon Seneschal", "Majestrix"}, + {"Amazon Regent", "Imperatrix"}}, + new String[]{"Despot Rule", "Common Rule", "Gynarchy", "Gynocracy"}), + NOBLE("Noble House", new String[][]{ + {"Serf"}, + {"Vassal"}, + {"Exultant"}, + {"Lord", "Lady"}, + {"Baron", "Baroness"}, + {"Count", "Countess"}, + {"Duke", "Duchess"}, + {"King", "Queen"}, + {"Emperor", "Empress"}}, + new String[]{"Monarchy", "Common Rule", "Feodality", "Republic"}), + WIZARD("Wizard's Conclave", new String[][]{ + {"Apprentice"}, + {"Neophyte"}, + {"Adeptus Minor"}, + {"Adeptus Major"}, + {"Magus"}, + {"High Magus"}, + {"Archmagus"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Magocracy"}), + MERCENARY("Mercenary Company", new String[][]{ + {"Soldier"}, + {"Man-at-Arms"}, + {"Veteran"}, + {"Myrmidon"}, + {"Captain"}, + {"Commander"}, + {"High Commander"}, + {"Warlord"}}, + new String[]{"Magistrature", "Mob Law", "Council Rule", "Republic Rule"}), + THIEVES("Thieve's Den", new String[][]{ + {"Urchin"}, + {"Footpad"}, + {"Grifter"}, + {"Burglar"}, + {"Collector"}, + {"Naster Thief"}, + {"Treasurer"}, + {"Grandmaster Thief"}, + {"Grandfather"}}, + new String[]{"Despot Rule", "Common Rule", "Oligarchy", "Republic Rule"}), + DWARF("Dwarf Hold", new String[][]{ + {"Citizen"}, + {"Master"}, + {"Councilor"}, + {"Thane"}, + {"Great Thane"}, + {"High Thane"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + HIGHCOURT("High Court", new String[][]{ + {"Eccekebe"}, + {"Saedulor"}, + {"Hodrimarth"}, + {"Mandrae"}, + {"Imaelin"}, + {"Thaelostor", "Thaelostril"}, + {"Dar Thaelostor", "Dar Thaelostril"}, + {"Aglaeron"}, + {"Ellestor", "Elestril"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + VIRAKT("Virakt", new String[][]{ + {"Jov'uus"}, + {"Urikhan"}, + {"Irkhan"}, + {"Khal'usht"}, + {"Arkhalar"}, + {"Khal'uvho"}, + {"Khar'uus"}, + {"Kryqh'khalin"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + BRIALIA("Coven of Brialia", new String[][]{ // Unknown Rank names + {"Devotee"}, + {"Initiated"}, + {"Witch of the First"}, + {"Witch of the Second"}, + {"Witch of the Third"}, + {"Elder"}, + {"Hierophant"}, + {"Witch King", "Witch Queen"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + UNHOLY("Unholy Legion", new String[][]{ // Unknown Rank names + {"Footman"}, + {"Fell Legionaire"}, + {"Fell Centurion"}, + {"Dark Captain"}, + {"Dark Commander"}, + {"Dark Master", "Dark Mistress"}, + {"Dread Master", "Dread Mistress"}, + {"Dread Lord", "Dread Lady"}}, + new String[]{"Despot Rule", "Despot Rule", "Council Rule", "Republic Rule"}), + SCOURGE("Cult of the Scourge", new String[][]{ + {"Thrall"}, + {"Mudir"}, + {"Dark Brother", "Dark Sister"}, + {"Hand of the Dark"}, + {"Dark Father", "Dark Mother"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + PIRATE("Pirate Crew", new String[][]{ + {"Midshipman", "Midshipwoman"}, + {"Sailor"}, + {"Third Mat"}, + {"Second Mat"}, + {"First Mate"}, + {"Captain"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + HERALD("Academy of Heralds", new String[][]{ + {"Pupil"}, + {"Scribe"}, + {"Recorder"}, + {"Scrivener"}, + {"Chronicler"}, + {"Scholar"}, + {"Archivist"}, + {"Loremaster"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + CENTAUR("Centaur Cohort", new String[][]{ + {"Hoplite"}, + {"Peltast"}, + {"Myrmidon"}, + {"Myrmidon"}, + {"Cataphract"}, + {"Septenrion"}, + {"Praetorian"}, + {"Paragon"}}, + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}), + KHREE("Aracoix Kh'ree", new String[][]{ + {"Duriacor"}, + {"Exarch"}, + {"Tetrarch"}, + {"Dimarch"}, + {"Elnarch"}, + {"Illiarch"}, + {"Tellotharch"}, + {"Erentar"}, + {"Araceos"}, + {"Hierarch"}}, + + new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}); + + private final String name; + private final String[][] ranks; //Stored Rank#->Gender(M,F) + private final String[] leadershipTypes; + + GuildType(String name, String[][] ranks, String[] leadershipTypes) { + this.name = name; + this.ranks = ranks; + this.leadershipTypes = leadershipTypes; + } + + public static GuildType getGuildTypeFromCharter(ItemBase itemBase) { + + GuildType charterType; + + // Must be a valid charter object + + if (itemBase.getType().equals(ItemType.GUILDCHARTER) == false) + return GuildType.NONE; //No guild Type + + // No switches on long in java. Cast to int + // when refactor to long uuid's. Loss won't matter + // with values this small. + + switch (itemBase.getUUID()) { + + case 559: + charterType = GuildType.CATHEDRAL; + break; + case 560: + charterType = GuildType.MILITARY; + break; + case 561: + charterType = GuildType.TEMPLE; + break; + case 562: + charterType = GuildType.BARBARIAN; + break; + case 563: + charterType = GuildType.RANGER; + break; + case 564: + charterType = GuildType.AMAZON; + break; + case 565: + charterType = GuildType.NOBLE; + break; + case 566: + charterType = GuildType.WIZARD; + break; + case 567: + charterType = GuildType.MERCENARY; + break; + case 568: + charterType = GuildType.THIEVES; + break; + case 569: + charterType = GuildType.DWARF; + break; + case 570: + charterType = GuildType.HIGHCOURT; + break; + case 571: + charterType = GuildType.VIRAKT; + break; + case 572: + charterType = GuildType.SCOURGE; + break; + case 573: + charterType = GuildType.KHREE; + break; + case 574: + charterType = GuildType.CENTAUR; + break; + case 575: + charterType = GuildType.UNHOLY; + break; + case 576: + charterType = GuildType.PIRATE; + break; + case 577: + charterType = GuildType.BRIALIA; + break; + + default: + charterType = GuildType.HERALD; + } + + return charterType; + } + + public static GuildType getGuildTypeFromInt(int i) { + return GuildType.values()[i]; + } + + public String getCharterName() { + return this.name; + } + + public int getNumberOfRanks() { + return ranks.length; + } + + public String getRankForGender(int rank, boolean male) { + if (ranks.length < rank) { + return ""; + } + + if (ranks[rank].length != 1 && !male) { + return ranks[rank][1]; + } + return ranks[rank][0]; + } + + public String getLeadershipType(int i) { + return leadershipTypes[i]; + } + + } + + public enum MinionClass { + MELEE, + ARCHER, + MAGE; + } + + public enum MinionType { + AELFBORNGUARD(951, 1637, MinionClass.MELEE, "Guard", "Aelfborn"), + AELFBORNMAGE(952, 1635, MinionClass.MAGE, "Adept", "Aelfborn"), + AMAZONGUARD(1500, 1670, MinionClass.MELEE, "Guard", "Amazon"), + AMAZONMAGE(1502, 1638, MinionClass.MAGE, "Fury", "Amazon"), + ARACOIXGUARD(1600, 1672, MinionClass.MELEE, "Guard", "Aracoix"), //used guard captain equipset. + ARACOIXMAGE(1602, 885, MinionClass.MAGE, "Adept", "Aracoix"), + CENTAURGUARD(1650, 1642, MinionClass.MELEE, "Guard", "Centaur"), + CENTAURMAGE(1652, 1640, MinionClass.MAGE, "Druid", "Centaur"), + DWARVENARCHER(845, 1644, MinionClass.ARCHER, "Marksman", "Dwarven"), + DWARVENGUARD(1050, 1666, MinionClass.MELEE, "Guard", "Dwarven"), + DWARVENMAGE(1052, 1643, MinionClass.MAGE, "War Priest", "Dwarven"), + ELFGUARD(1180, 1671, MinionClass.MELEE, "Guard", "Elven"), //old 1645 + ELFMAGE(1182, 1667, MinionClass.MAGE, "Adept", "Elven"), + FORESTGUARD(1550, 1668, MinionClass.MELEE, "Guard", "Forest"), //captain changed to guard equipset + FORESTMAGE(1552, 436, MinionClass.MAGE, "Adept", "Forest"), + HOLYGUARD(1525, 1658, MinionClass.MELEE, "Guard", "Holy Church"), + HOLYMAGE(1527, 1646, MinionClass.MAGE, "Prelate", "Holy Church"), + HUMANARCHER(846, 1654, MinionClass.ARCHER, "Archer", "Human"), + HUMANGUARD(840, 1665, MinionClass.MELEE, "Guard", "Human"), + HUMANMAGE(848, 1655, MinionClass.MAGE, "Adept", "Human"), + IREKEIGUARD(1350, 1659, MinionClass.MELEE, "Guard", "Irekei"), + IREKEIMAGE(1352, 1660, MinionClass.MAGE, "Adept", "Irekei"), + MINOTAURARCHER(1701, 0, MinionClass.ARCHER, "Archer", "Minotaur"), + MINOTAURGUARD(1700, 1673, MinionClass.MELEE, "Guard", "Minotaur"), + NORTHMANGUARD(1250, 1669, MinionClass.MELEE, "Guard", "Northman"), + NORTHMANMAGE(1252, 1650, MinionClass.MAGE, "Runecaster", "Northman"), + SHADEGUARD(1450, 1662, MinionClass.MELEE, "Guard", "Shade"), + SHADEMAGE(1452, 1664, MinionClass.MAGE, "Adept", "Shade"), + TEMPLARGUARD(841, 1564, MinionClass.MELEE, "Marksman", "Templar"), + TEMPLEGUARD(1575, 1652, MinionClass.MELEE, "Guard", "Temple"), + TEMPLEMAGE(1577, 1656, MinionClass.MAGE, "Confessor", "Temple"), + UNDEADGUARD(980100, 1674, MinionClass.MELEE, "Guard", "Undead"), + UNDEADMAGE(980102, 1675, MinionClass.MAGE, "Adept", "Undead"), + WEREWOLFGUARD(980104, 0, MinionClass.MELEE, "Guard", "Werewolf"), + WEREBEARGUARD(980103, 0, MinionClass.MELEE, "Guard", "Werebear"); + public static HashMap ContractToMinionMap = new HashMap<>(); + private final int captainContractID; + private final int equipSetID; + private final MinionClass minionClass; + private final String name; + private final String race; + + MinionType(int captainContractID, int equipSetID, MinionClass minionClass, String name, String race) { + + this.captainContractID = captainContractID; + this.equipSetID = equipSetID; + this.minionClass = minionClass; + this.name = name; + this.race = race; + + } + + public static void InitializeMinions() { + + for (MinionType minionType : MinionType.values()) + ContractToMinionMap.put(minionType.captainContractID, minionType); + } + + public String getName() { + return name; + } + + public String getRace() { + return race; + } + + public Boolean isMage(){ + return this.minionClass.ordinal() == MinionClass.MAGE.ordinal(); + } + } + + public enum GridObjectType { + STATIC, + DYNAMIC; + } + + public enum SupportMsgType { + NONE(0), + PROTECT(1), + UNPROTECT(3), + VIEWUNPROTECTED(4), + REMOVETAX(6), + ACCEPTTAX(7), + CONFIRMPROTECT(8); + + public static HashMap typeLookup = new HashMap<>(); + private final int type; + + SupportMsgType(int messageType) { + this.type = messageType; + + } + + public static void InitializeSupportMsgType() { + + for (SupportMsgType supportMsgType : SupportMsgType.values()) + typeLookup.put(supportMsgType.type, supportMsgType); + } + } + + public enum ResourceType implements EnumBitSetHelper { + + STONE(1580000), + TRUESTEEL(1580001), + IRON(1580002), + ADAMANT(1580003), + LUMBER(1580004), + OAK(1580005), + BRONZEWOOD(1580006), + MANDRAKE(1580007), + COAL(1580008), + AGATE(1580009), + DIAMOND(1580010), + ONYX(1580011), + AZOTH(1580012), + ORICHALK(1580013), + ANTIMONY(1580014), + SULFUR(1580015), + QUICKSILVER(1580016), + GALVOR(1580017), + WORMWOOD(1580018), + OBSIDIAN(1580019), + BLOODSTONE(1580020), + MITHRIL(1580021), + GOLD(7); + + public static HashMap resourceLookup = new HashMap<>(); + public int itemID; + + ResourceType(int itemID) { + this.itemID = itemID; + } + + public static void InitializeResourceTypes() { + + for (ResourceType resourceType : ResourceType.values()) + resourceLookup.put(resourceType.itemID, resourceType); + } + } + + public enum PowerActionType { + ApplyEffect, + ApplyEffects, + Block, + Charm, + ClaimMine, + ClearAggro, + ClearNearbyAggro, + Confusion, + CreateMob, + DamageOverTime, + DeferredPower, + DirectDamage, + Invis, + MobRecall, + Peek, + Recall, + RemoveEffect, + Resurrect, + RunegateTeleport, + SetItemFlag, + SimpleDamage, + SpireDisable, + Steal, + Summon, + Teleport, + Track, + TransferStat, + TransferStatOT, + Transform, + TreeChoke + } + + public enum AccountStatus { + BANNED, + ACTIVE, + ADMIN; + } + + public enum MobBehaviourType { + None(null, false, false, false, false, false), + //Power + Power(null, false, true, true, true, false), + PowerHelpee(Power, false, true, true, false, true), + PowerHelpeeWimpy(Power, true, false, true, false, false), + PowerGrouperWimpy(Power, true, false, true, false, false), + PowerAggro(Power, false, true, true, false, true), + PowerAggroHelpee(Power, false, true, true, false, true), + //Aggro + Aggro(null, false, true, true, true, false), + AggroHelpee(Aggro, false, true, true, false, true), + AggroHelpeeWimpy(Aggro, true, false, true, false, false), + AggroGrouperWimpy(Aggro, true, false, true, false, false), + //Spell + Spell(null, false, true, true, true, false), + SpellHelpee(Spell, false, true, true, false, true), + SpellHelpeeWimpy(Spell, true, false, true, false, false), + SpellGrouperWimpy(Spell, true, false, true, false, false), + SpellAggro(Spell, false, true, true, false, true), + SpellAggroHelpee(Spell, false, true, true, false, true), + SpellAggroHelpeeWimpy(Spell, true, false, true, false, false), + SpellAggroHelpeeEpic(Spell, false, true, true, false, true), + SpellAggroGrouperWimpy(Spell, true, false, true, false, false), + //Independent Types + SimpleStandingGuard(null, false, false, false, false, false), + Pet1(null, false, false, true, false, false), + Simple(null, false, false, true, false, false), + Helpee(null, false, true, true, false, true), + HelpeeWimpy(null, true, false, true, false, false), + GuardCaptain(null, false, true, true, false, false), + GuardMinion(GuardCaptain, false, true, true, false, false), + GuardWallArcher(null, false, true, false, false, false), + Wanderer(null, false, true, true, false, false), + HamletGuard(null, false, true, false, false, false), + AggroWanderer(null, false, false, true, false, false); + + private static HashMap _behaviourTypes = new HashMap<>(); + public MobBehaviourType BehaviourHelperType; + public boolean isWimpy; + public boolean isAgressive; + public boolean canRoam; + public boolean callsForHelp; + public boolean respondsToCallForHelp; + + MobBehaviourType(MobBehaviourType helpeebehaviourType, boolean wimpy, boolean agressive, boolean canroam, boolean callsforhelp, boolean respondstocallforhelp) { + this.BehaviourHelperType = helpeebehaviourType; + this.isWimpy = wimpy; + this.isAgressive = agressive; + this.canRoam = canroam; + this.callsForHelp = callsforhelp; + this.respondsToCallForHelp = respondstocallforhelp; + } + + } + + public enum AIAgentType { + MOBILE, + PET, + CHARMED, + GUARD; + } +} diff --git a/src/engine/InterestManagement/HeightMap.java b/src/engine/InterestManagement/HeightMap.java new file mode 100644 index 00000000..3909028d --- /dev/null +++ b/src/engine/InterestManagement/HeightMap.java @@ -0,0 +1,706 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.InterestManagement; + +import engine.Enum; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector2f; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractWorldObject; +import engine.objects.Zone; +import engine.util.MapLoader; +import org.pmw.tinylog.Logger; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + +public class HeightMap { + + // Class variables + + // Heightmap data for all zones. + + public static final HashMap heightmapByLoadNum = new HashMap<>(); + + // Bootstrap Tracking + + public static int heightMapsCreated = 0; + public static HeightMap PlayerCityHeightMap; + + // Heightmap data for this heightmap + + public BufferedImage heightmapImage; + + private int heightMapID; + private int maxHeight; + private int fullExtentsX; + private int fullExtentsY; + + private float bucketWidthX; + private float bucketWidthY; + private int zoneLoadID; + private float seaLevel = 0; + private float outsetX; + private float outsetZ; + private int[][] pixelColorValues; + + public HeightMap(ResultSet rs) throws SQLException { + + this.heightMapID = rs.getInt("heightMapID"); + this.maxHeight = rs.getInt("maxHeight"); + int halfExtentsX = rs.getInt("xRadius"); + int halfExtentsY = rs.getInt("zRadius"); + this.zoneLoadID = rs.getInt("zoneLoadID"); + this.seaLevel = rs.getFloat("seaLevel"); + this.outsetX = rs.getFloat("outsetX"); + this.outsetZ = rs.getFloat("outsetZ"); + + + // Cache the full extents to avoid the calculation + + this.fullExtentsX = halfExtentsX * 2; + this.fullExtentsY = halfExtentsY * 2; + + this.heightmapImage = null; + File imageFile = new File(ConfigManager.DEFAULT_DATA_DIR + "heightmaps/" + this.heightMapID + ".bmp"); + + // early exit if no image file was found. Will log in caller. + + if (!imageFile.exists()) + return; + + // load the heightmap image. + + try { + this.heightmapImage = ImageIO.read(imageFile); + } catch (IOException e) { + Logger.error("***Error loading heightmap data for heightmap " + this.heightMapID + e.toString()); + } + + // We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin. + + this.heightmapImage = MapLoader.flipImage(this.heightmapImage); + + // Calculate the data we do not load from table + + float numOfBuckets = this.heightmapImage.getWidth() - 1; + float calculatedWidth = this.fullExtentsX / numOfBuckets; + this.bucketWidthX = calculatedWidth; + this.bucketWidthY = this.bucketWidthX; // This makes no sense. + + // Generate pixel array from image data + + generatePixelData(); + + HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this); + + heightMapsCreated++; + } + + //Created for PlayerCities + public HeightMap() { + + this.heightMapID = 999999; + this.maxHeight = 5; // for real... + int halfExtentsX = (int) Enum.CityBoundsType.ZONE.extents; + int halfExtentsY = (int) Enum.CityBoundsType.ZONE.extents; + this.zoneLoadID = 0; + this.seaLevel = 0; + this.outsetX = 128; + this.outsetZ = 128; + + + // Cache the full extents to avoid the calculation + + this.fullExtentsX = halfExtentsX * 2; + this.fullExtentsY = halfExtentsY * 2; + + + // load the heightmap image. + + + // We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin. + + this.heightmapImage = null; + + // Calculate the data we do not load from table + + this.bucketWidthX = 1; + this.bucketWidthY = 1; + + this.pixelColorValues = new int[this.fullExtentsX + 1][this.fullExtentsY + 1]; + + for (int y = 0; y <= this.fullExtentsY; y++) { + for (int x = 0; x <= this.fullExtentsX; x++) { + pixelColorValues[x][y] = 255; + } + } + + + HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this); + } + + public HeightMap(Zone zone) { + + this.heightMapID = 999999; + this.maxHeight = 0; + int halfExtentsX = (int) zone.getBounds().getHalfExtents().x; + int halfExtentsY = (int) zone.getBounds().getHalfExtents().y; + this.zoneLoadID = 0; + this.seaLevel = 0; + this.outsetX = 0; + this.outsetZ = 0; + + // Cache the full extents to avoid the calculation + + this.fullExtentsX = halfExtentsX * 2; + this.fullExtentsY = halfExtentsY * 2; + + + // We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin. + + this.heightmapImage = null; + + // Calculate the data we do not load from table + + this.bucketWidthX = 1; + this.bucketWidthY = 1; + + this.pixelColorValues = new int[this.fullExtentsX + 1][this.fullExtentsY + 1]; + + for (int y = 0; y <= this.fullExtentsY; y++) { + for (int x = 0; x <= this.fullExtentsX; x++) { + pixelColorValues[x][y] = 255; + } + } + + + HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this); + } + + public static void GeneratePlayerCityHeightMap() { + + HeightMap.PlayerCityHeightMap = new HeightMap(); + + } + + public static void GenerateCustomHeightMap(Zone zone) { + + HeightMap heightMap = new HeightMap(zone); + + HeightMap.heightmapByLoadNum.put(zone.getLoadNum(), heightMap); + + } + + public static Zone getNextZoneWithTerrain(Zone zone) { + + Zone nextZone = zone; + + if (zone.getHeightMap() != null) + return zone; + + if (zone.equals(ZoneManager.getSeaFloor())) + return zone; + + while (nextZone.getHeightMap() == null) + nextZone = nextZone.getParent(); + + return nextZone; + } + + public static float getWorldHeight(AbstractWorldObject worldObject) { + + Vector2f parentLoc = new Vector2f(-1, -1); + Zone currentZone = ZoneManager.findSmallestZone(worldObject.getLoc()); + + if (currentZone == null) + return worldObject.getAltitude(); + + currentZone = getNextZoneWithTerrain(currentZone); + + if (currentZone == ZoneManager.getSeaFloor()) + return currentZone.getAbsY() + worldObject.getAltitude(); + + Zone parentZone = getNextZoneWithTerrain(currentZone.getParent()); + HeightMap heightMap = currentZone.getHeightMap(); + + Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldObject.getLoc(), currentZone); + Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldObject.getLoc(), currentZone); + + if ((parentZone != null) && (parentZone.getHeightMap() != null)) + parentLoc = ZoneManager.worldToZoneSpace(worldObject.getLoc(), parentZone); + + float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc); + + float worldAltitude = currentZone.getWorldAltitude(); + + float realWorldAltitude = interaltitude + worldAltitude; + + //OUTSET + + if (parentZone != null) { + + float parentXRadius = currentZone.getBounds().getHalfExtents().x; + float parentZRadius = currentZone.getBounds().getHalfExtents().y; + + float offsetX = Math.abs((localLocFromCenter.x / parentXRadius)); + float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius)); + + float bucketScaleX = heightMap.outsetX / parentXRadius; + float bucketScaleZ = heightMap.outsetZ / parentZRadius; + + if (bucketScaleX <= 0.40000001) + bucketScaleX = heightMap.outsetZ / parentXRadius; + + if (bucketScaleX > 0.40000001) + bucketScaleX = 0.40000001f; + + if (bucketScaleZ <= 0.40000001) + bucketScaleZ = heightMap.outsetX / parentZRadius; + + if (bucketScaleZ > 0.40000001) + bucketScaleZ = 0.40000001f; + + float outsideGridSizeX = 1 - bucketScaleX; //32/256 + float outsideGridSizeZ = 1 - bucketScaleZ; + float weight; + + double scale; + + if (offsetX > outsideGridSizeX && offsetX > offsetZ) { + weight = (offsetX - outsideGridSizeX) / bucketScaleX; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + + float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone)); + + parentCenterAltitude += currentZone.getYCoord(); + parentCenterAltitude += interaltitude; + + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + float outsetALt = firstScale + secondScale; + + outsetALt += currentZone.getParent().getWorldAltitude(); + realWorldAltitude = outsetALt; + + } else if (offsetZ > outsideGridSizeZ) { + + weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone)); + + parentCenterAltitude += currentZone.getYCoord(); + parentCenterAltitude += interaltitude; + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + float outsetALt = firstScale + secondScale; + + outsetALt += currentZone.getParent().getWorldAltitude(); + realWorldAltitude = outsetALt; + } + } + + return realWorldAltitude; + } + + public static float getWorldHeight(Vector3fImmutable worldLoc) { + + Vector2f parentLoc = new Vector2f(-1, -1); + Zone currentZone = ZoneManager.findSmallestZone(worldLoc); + + if (currentZone == null) + return 0; + + currentZone = getNextZoneWithTerrain(currentZone); + + if (currentZone == ZoneManager.getSeaFloor()) + return currentZone.getAbsY(); + + Zone parentZone = getNextZoneWithTerrain(currentZone.getParent()); + HeightMap heightMap = currentZone.getHeightMap(); + + if ((heightMap == null) || (currentZone == ZoneManager.getSeaFloor())) + return currentZone.getAbsY(); + + Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldLoc, currentZone); + Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldLoc, currentZone); + + if ((parentZone != null) && (parentZone.getHeightMap() != null)) + parentLoc = ZoneManager.worldToZoneSpace(worldLoc, parentZone); + + float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc); + + float worldAltitude = currentZone.getWorldAltitude(); + + float realWorldAltitude = interaltitude + worldAltitude; + + //OUTSET + + if (parentZone != null) { + + // if (currentZone.getHeightMap() != null && parentZone.getHeightMap() != null && parentZone.getParent() != null && parentZone.getParent().getHeightMap() != null) + // return realWorldAltitude; + + float parentXRadius = currentZone.getBounds().getHalfExtents().x; + float parentZRadius = currentZone.getBounds().getHalfExtents().y; + + float offsetX = Math.abs((localLocFromCenter.x / parentXRadius)); + float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius)); + + float bucketScaleX = heightMap.outsetX / parentXRadius; + float bucketScaleZ = heightMap.outsetZ / parentZRadius; + + float outsideGridSizeX = 1 - bucketScaleX; //32/256 + float outsideGridSizeZ = 1 - bucketScaleZ; + float weight; + + double scale; + + + if (offsetX > outsideGridSizeX && offsetX > offsetZ) { + weight = (offsetX - outsideGridSizeX) / bucketScaleX; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + + float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone)); + + parentCenterAltitude += currentZone.getYCoord(); + parentCenterAltitude += interaltitude; + + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + float outsetALt = firstScale + secondScale; + + outsetALt += currentZone.getParent().getWorldAltitude(); + realWorldAltitude = outsetALt; + + } else if (offsetZ > outsideGridSizeZ) { + + weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone)); + + parentCenterAltitude += currentZone.getYCoord(); + parentCenterAltitude += interaltitude; + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + float outsetALt = firstScale + secondScale; + + outsetALt += currentZone.getParent().getWorldAltitude(); + realWorldAltitude = outsetALt; + } + } + + return realWorldAltitude; + } + + public static float getOutsetHeight(float interpolatedAltitude, Zone zone, Vector3fImmutable worldLocation) { + + Vector2f parentLoc; + float outsetALt = 0; + + if (zone.getParent() == null || zone.getParent().getHeightMap() == null) + return interpolatedAltitude + zone.getWorldAltitude(); + + if (zone.getParent() != null && zone.getParent().getHeightMap() != null) { + + parentLoc = ZoneManager.worldToZoneSpace(worldLocation, zone.getParent()); + + Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldLocation, zone); + + float parentXRadius = zone.getBounds().getHalfExtents().x; + float parentZRadius = zone.getBounds().getHalfExtents().y; + + float bucketScaleX = zone.getHeightMap().outsetX / parentXRadius; + float bucketScaleZ = zone.getHeightMap().outsetZ / parentZRadius; + + float outsideGridSizeX = 1 - bucketScaleX; //32/256 + float outsideGridSizeZ = 1 - bucketScaleZ; + + float weight; + double scale; + + float offsetX = Math.abs((localLocFromCenter.x / parentXRadius)); + float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius)); + + if (offsetX > outsideGridSizeX && offsetX > offsetZ) { + weight = (offsetX - outsideGridSizeX) / bucketScaleX; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + + float parentAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(zone.getLoc(), zone.getParent())); + + parentCenterAltitude += zone.getYCoord(); + parentCenterAltitude += interpolatedAltitude; + + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + outsetALt = firstScale + secondScale; + + outsetALt += zone.getParent().getAbsY(); + + } else if (offsetZ > outsideGridSizeZ) { + + weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + float parentAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = zone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(zone.getLoc(), zone)); + + parentCenterAltitude += zone.getYCoord(); + parentCenterAltitude += interpolatedAltitude; + + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + outsetALt = firstScale + secondScale; + + outsetALt += zone.getParent().getAbsY(); + } + + } + + return outsetALt; + } + + public static Vector2f getGridOffset(Vector2f gridSquare) { + + int floorX = (int) gridSquare.x; + int floorY = (int) gridSquare.y; + + return new Vector2f(gridSquare.x - floorX, gridSquare.y - floorY); + + } + + public static void loadAlHeightMaps() { + + // Load the heightmaps into staging hashmap keyed by HashMapID + + DbManager.HeightMapQueries.LOAD_ALL_HEIGHTMAPS(); + + //generate static player city heightmap. + + HeightMap.GeneratePlayerCityHeightMap(); + + + // Clear all heightmap image data as it's no longer needed. + + for (HeightMap heightMap : HeightMap.heightmapByLoadNum.values()) { + heightMap.heightmapImage = null; + } + + Logger.info(HeightMap.heightmapByLoadNum.size() + " Heightmaps cached."); + } + + public static boolean isLocUnderwater(Vector3fImmutable currentLoc) { + + float localAltitude = HeightMap.getWorldHeight(currentLoc); + Zone zone = ZoneManager.findSmallestZone(currentLoc); + + if (localAltitude < zone.getSeaLevel()) + return true; + + return false; + } + + public Vector2f getGridSquare(Vector2f zoneLoc) { + + if (zoneLoc.x < 0) + zoneLoc.setX(0); + + if (zoneLoc.x > this.fullExtentsX - 1) + zoneLoc.setX((this.fullExtentsX - 1) + .9999999f); + + if (zoneLoc.y < 0) + zoneLoc.setY(0); + + if (zoneLoc.y > this.fullExtentsY - 1) + zoneLoc.setY((this.fullExtentsY - 1) + .9999999f); + + float xBucket = (zoneLoc.x / this.bucketWidthX); + float yBucket = (zoneLoc.y / this.bucketWidthY); + + return new Vector2f(xBucket, yBucket); + } + + public float getInterpolatedTerrainHeight(Vector2f zoneLoc) { + + Vector2f gridSquare; + + if (zoneLoc.x < 0 || zoneLoc.x > this.fullExtentsX) + return -1; + + if (zoneLoc.y < 0 || zoneLoc.y > this.fullExtentsY) + return -1; + + int maxX = (int) (this.fullExtentsX / this.bucketWidthX); + int maxY = (int) (this.fullExtentsY / this.bucketWidthY); + + //flip the Y so it grabs from the bottom left instead of top left. + //zoneLoc.setY(maxZoneHeight - zoneLoc.y); + + gridSquare = getGridSquare(zoneLoc); + + int gridX = (int) gridSquare.x; + int gridY = (int) (gridSquare.y); + + if (gridX > maxX) + gridX = maxX; + if (gridY > maxY) + gridY = maxY; + + float offsetX = (gridSquare.x - gridX); + float offsetY = gridSquare.y - gridY; + + //get height of the 4 vertices. + + float topLeftHeight = 0; + float topRightHeight = 0; + float bottomLeftHeight = 0; + float bottomRightHeight = 0; + + int nextY = gridY + 1; + int nextX = gridX + 1; + + if (nextY > maxY) + nextY = gridY; + + if (nextX > maxX) + nextX = gridX; + + topLeftHeight = pixelColorValues[gridX][gridY]; + topRightHeight = pixelColorValues[nextX][gridY]; + bottomLeftHeight = pixelColorValues[gridX][nextY]; + bottomRightHeight = pixelColorValues[nextX][nextY]; + + float interpolatedHeight; + + interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX); + interpolatedHeight += (bottomRightHeight * offsetY * offsetX); + interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY); + interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY)); + + interpolatedHeight *= (float) this.maxHeight / 256; // Scale height + + return interpolatedHeight; + } + + public float getInterpolatedTerrainHeight(Vector3fImmutable zoneLoc3f) { + + Vector2f zoneLoc = new Vector2f(zoneLoc3f.x, zoneLoc3f.z); + + Vector2f gridSquare; + + if (zoneLoc.x < 0 || zoneLoc.x > this.fullExtentsX) + return -1; + + if (zoneLoc.y < 0 || zoneLoc.y > this.fullExtentsY) + return -1; + + //flip the Y so it grabs from the bottom left instead of top left. + //zoneLoc.setY(maxZoneHeight - zoneLoc.y); + + gridSquare = getGridSquare(zoneLoc); + + int gridX = (int) gridSquare.x; + int gridY = (int) (gridSquare.y); + + float offsetX = (gridSquare.x - gridX); + float offsetY = gridSquare.y - gridY; + + //get height of the 4 vertices. + + float topLeftHeight = pixelColorValues[gridX][gridY]; + float topRightHeight = pixelColorValues[gridX + 1][gridY]; + float bottomLeftHeight = pixelColorValues[gridX][gridY + 1]; + float bottomRightHeight = pixelColorValues[gridX + 1][gridY + 1]; + + float interpolatedHeight; + + interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX); + interpolatedHeight += (bottomRightHeight * offsetY * offsetX); + interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY); + interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY)); + + interpolatedHeight *= (float) this.maxHeight / 256; // Scale height + + return interpolatedHeight; + } + + private void generatePixelData() { + + Color color; + + // Generate altitude lookup table for this heightmap + + this.pixelColorValues = new int[this.heightmapImage.getWidth()][this.heightmapImage.getHeight()]; + + for (int y = 0; y < this.heightmapImage.getHeight(); y++) { + for (int x = 0; x < this.heightmapImage.getWidth(); x++) { + + color = new Color(this.heightmapImage.getRGB(x, y)); + pixelColorValues[x][y] = color.getRed(); + } + } + + } + + public float getScaledHeightForColor(float color) { + + return (color / 256) * this.maxHeight; + } + + public float getBucketWidthX() { + return bucketWidthX; + } + + public float getBucketWidthY() { + return bucketWidthY; + } + + public int getHeightMapID() { + return heightMapID; + } + + public BufferedImage getHeightmapImage() { + return heightmapImage; + } + + public float getSeaLevel() { + return seaLevel; + } + +} diff --git a/src/engine/InterestManagement/InterestManager.java b/src/engine/InterestManagement/InterestManager.java new file mode 100644 index 00000000..ab6ea532 --- /dev/null +++ b/src/engine/InterestManagement/InterestManager.java @@ -0,0 +1,561 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.InterestManagement; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.Enum.GameObjectType; +import engine.gameManager.GroupManager; +import engine.gameManager.SessionManager; +import engine.job.JobScheduler; +import engine.jobs.RefreshGroupJob; +import engine.net.AbstractNetMsg; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.LoadCharacterMsg; +import engine.net.client.msg.LoadStructureMsg; +import engine.net.client.msg.MoveToPointMsg; +import engine.net.client.msg.UnloadObjectsMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashSet; + +import static engine.math.FastMath.sqr; + +public enum InterestManager implements Runnable { + + INTERESTMANAGER; + + private static long lastTime; + + InterestManager() { + Logger.info(" Interest Management thread is running."); + } + + public static void forceLoad(AbstractWorldObject awo) { + + AbstractNetMsg msg = null; + LoadStructureMsg lsm; + LoadCharacterMsg lcm; + NPC npc; + Corpse corpse; + HashSet toUpdate; + + switch (awo.getObjectType()) { + case Building: + lsm = new LoadStructureMsg(); + lsm.addObject((Building) awo); + msg = lsm; + break; + case Corpse: + corpse = (Corpse) awo; + lcm = new LoadCharacterMsg(corpse, false); + msg = lcm; + break; + case NPC: + npc = (NPC) awo; + lcm = new LoadCharacterMsg(npc, false); + msg = lcm; + break; + default: + return; + } + + toUpdate = WorldGrid.getObjectsInRangePartial(awo.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_PLAYER); + + boolean send; + + for (AbstractWorldObject tar : toUpdate) { + PlayerCharacter player = (PlayerCharacter) tar; + HashSet loadedStaticObjects = player.getLoadedStaticObjects(); + send = false; + + if (!loadedStaticObjects.contains(awo)) { + loadedStaticObjects.add(awo); + send = true; + } + + if (send) { + + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + } + } + + public static void HandleSpecialUnload(Building building, ClientConnection origin) { + + if (Regions.FurnitureRegionMap.get(building.getObjectUUID()) == null) + return; + + Regions buildingRegion = Regions.FurnitureRegionMap.get(building.getObjectUUID()); + + if (!buildingRegion.isOutside()) + return; + + MoveToPointMsg moveMsg = new MoveToPointMsg(building); + + if (origin != null) + origin.sendMsg(moveMsg); + } + + public static void reloadCharacter(AbstractCharacter absChar) { + + UnloadObjectsMsg uom = new UnloadObjectsMsg(); + uom.addObject(absChar); + LoadCharacterMsg lcm = new LoadCharacterMsg(absChar, false); + + HashSet toSend = WorldGrid.getObjectsInRangePartial(absChar.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, + MBServerStatics.MASK_PLAYER); + + PlayerCharacter pc = null; + + if (absChar.getObjectType().equals(GameObjectType.PlayerCharacter)) + pc = (PlayerCharacter) absChar; + + for (AbstractWorldObject awo : toSend) { + + PlayerCharacter pcc = (PlayerCharacter) awo; + + if (pcc == null) + continue; + + ClientConnection cc = SessionManager.getClientConnection(pcc); + + if (cc == null) + continue; + + if (pcc.getObjectUUID() == absChar.getObjectUUID()) + continue; + + else { + if (pc != null) + if (pcc.getSeeInvis() < pc.getHidden()) + continue; + + if (!cc.sendMsg(uom)) { + String classType = uom.getClass().getSimpleName(); + Logger.error("Failed to send message "); + } + + if (!cc.sendMsg(lcm)) { + String classType = lcm.getClass().getSimpleName(); + Logger.error("Failed to send message"); + } + } + } + } + + @Override + public void run() { + beginLoadJob(); + } + + private void beginLoadJob() { + + InterestManager.lastTime = System.currentTimeMillis(); + + while (true) { + try { + updateAllPlayers(); + } catch (Exception e) { + Logger.error("InterestManager.BeginLoadJob:updateAllPlayers", e); + } + try { + Thread.sleep(advanceOneSecond()); + } catch (Exception e) { + Logger.error("InterestManager.BeginLoadJob:advanceOneSecond", e); + } + } + } + + private long advanceOneSecond() { + + long curTime = System.currentTimeMillis(); + long dur = 1000 + this.lastTime - curTime; + + if (dur < 0) { + // Last update took more then one second, not good... + Logger.warn("LoadJob took more then one second to complete."); + this.lastTime = curTime + 100; + return 100; + } + this.lastTime += 1000; + return dur; + } + + // Forces the loading of static objects (corpses and buildings). + // Needed to override threshold limits on loading statics + + private void updateAllPlayers() { + // get all players + + for (PlayerCharacter playerCharacter : SessionManager.getAllActivePlayerCharacters()) { + + if (playerCharacter == null) + continue; + + ClientConnection origin = playerCharacter.getClientConnection(); + + if (origin == null) + continue; + + if (!playerCharacter.isEnteredWorld()) + continue; + + if (playerCharacter.getTeleportLock().readLock().tryLock()) { + + try { + updateStaticList(playerCharacter, origin); + updateMobileList(playerCharacter, origin); + } catch (Exception e) { + Logger.error(e); + } finally { + playerCharacter.getTeleportLock().readLock().unlock(); + } + } + } + } + + private void updateStaticList(PlayerCharacter player, ClientConnection origin) { + + + // Only update if we've moved far enough to warrant it + + float distanceSquared = player.getLoc().distanceSquared2D(player.getLastStaticLoc()); + + if (distanceSquared > sqr(64)) + player.setLastStaticLoc(player.getLoc()); + else if (player.isDirtyLoad() == false) + return; + + // Get Statics in range + + HashSet toLoad = WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.STRUCTURE_LOAD_RANGE, + MBServerStatics.MASK_STATIC); + + // get list of obects loaded that need removed + + HashSet loadedStaticObjects = player.getLoadedStaticObjects(); + + HashSet toRemove = null; + + toRemove = new HashSet<>(loadedStaticObjects); + + toRemove.removeAll(toLoad); + + // unload static objects now out of range + + if (toRemove.size() > 0) { + + UnloadObjectsMsg uom = new UnloadObjectsMsg(); + + for (AbstractWorldObject obj : toRemove) { + + if (obj.getObjectType().equals(GameObjectType.Building)) + InterestManager.HandleSpecialUnload((Building) obj, origin); + + if (obj != null && !obj.equals(player)) + uom.addObject(obj); + } + + Dispatch dispatch = Dispatch.borrow(player, uom); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + loadedStaticObjects.removeAll(toRemove); + + // remove any object to load that are already loaded + + toLoad.removeAll(loadedStaticObjects); + + LoadStructureMsg lsm = new LoadStructureMsg(); + LoadCharacterMsg lcm = null; + ArrayList lcmList = new ArrayList<>(); + + for (AbstractWorldObject awo : toLoad) { + + if (awo.getObjectType().equals(GameObjectType.Building)) + lsm.addObject((Building) awo); + else if (awo.getObjectType().equals(GameObjectType.Corpse)) { + Corpse corpse = (Corpse) awo; + lcm = new LoadCharacterMsg(corpse, PlayerCharacter.hideNonAscii()); + + Dispatch dispatch = Dispatch.borrow(player, lcm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + } else if (awo.getObjectType().equals(GameObjectType.NPC)) { + NPC npc = (NPC) awo; + lcm = new LoadCharacterMsg(npc, PlayerCharacter.hideNonAscii()); + + lcmList.add(lcm); + } + } + + if (lsm.getStructureList().size() > 0) { + Dispatch dispatch = Dispatch.borrow(player, lsm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + for (LoadCharacterMsg lc : lcmList) { + + Dispatch dispatch = Dispatch.borrow(player, lc); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + loadedStaticObjects.addAll(toLoad); + player.setDirtyLoad(false); + } + + private void updateMobileList(PlayerCharacter player, ClientConnection origin) { + + if (player == null) + return; + + // Get list of players in range + // TODO for now use a generic getALL list, later tie into Quad Tree + HashSet toLoad = WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, + MBServerStatics.MASK_MOBILE); + + HashSet toRemove = new HashSet<>(); + + HashSet toLoadToPlayer = new HashSet<>(); + + for (AbstractWorldObject loadedObject : toLoad) { + + switch (loadedObject.getObjectType()) { + case PlayerCharacter: + PlayerCharacter loadedPlayer = (PlayerCharacter) loadedObject; + + if (loadedPlayer.getObjectUUID() == player.getObjectUUID()) + continue; + + if (player.getSeeInvis() < loadedPlayer.getHidden()) + continue; + + if (loadedPlayer.safemodeInvis()) + continue; + + if (player.getLoadedObjects().contains(loadedPlayer)) + continue; + + if (!loadedPlayer.isInWorldGrid()) + continue; + + toLoadToPlayer.add(loadedPlayer); + break; + //not playerCharacter, mobs,npcs and corpses cant be invis or safemode, just add normaly + default: + if (player.getLoadedObjects().contains(loadedObject)) + continue; + + if (!loadedObject.isInWorldGrid()) + continue; + + toLoadToPlayer.add(loadedObject); + break; + } + } + + float unloadDistance = MBServerStatics.CHARACTER_LOAD_RANGE; + for (AbstractWorldObject playerLoadedObject : player.getLoadedObjects()) { + + if (playerLoadedObject.getObjectType().equals(GameObjectType.PlayerCharacter)) { + PlayerCharacter loadedPlayer = (PlayerCharacter) playerLoadedObject; + if (player.getSeeInvis() < loadedPlayer.getHidden()) + toRemove.add(playerLoadedObject); + else if (loadedPlayer.safemodeInvis()) + toRemove.add(playerLoadedObject); + } + + if (!playerLoadedObject.isInWorldGrid()) + toRemove.add(playerLoadedObject); + else if (playerLoadedObject.getLoc().distanceSquared2D(player.getLoc()) > unloadDistance * unloadDistance) + toRemove.add(playerLoadedObject); + + } + + player.getLoadedObjects().addAll(toLoadToPlayer); + player.getLoadedObjects().removeAll(toRemove); + + // get list of obects loaded to remove + + // unload objects now out of range + + if (toRemove.size() > 0) { + + UnloadObjectsMsg uom = new UnloadObjectsMsg(); + + for (AbstractWorldObject obj : toRemove) { + + try { + if (obj != null) + if (obj.equals(player)) // don't unload self + continue; + + uom.addObject(obj); + + if (obj.getObjectType() == GameObjectType.Mob) + ((Mob) obj).playerAgroMap.remove(player.getObjectUUID()); + } catch (Exception e) { + Logger.error("UnloadCharacter", obj.getObjectUUID() + " " + e.getMessage()); + } + } + + if (!uom.getObjectList().isEmpty()) { + Dispatch dispatch = Dispatch.borrow(player, uom); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + } + + LoadCharacterMsg lcm = null; + ArrayList players = new ArrayList<>(); + ArrayList addToList = new ArrayList<>(); + + for (AbstractWorldObject awo : toLoadToPlayer) { + + + try { + if (awo.equals(player)) // dont load yourself + continue; + + if ((awo.getObjectTypeMask() & MBServerStatics.MASK_PLAYER) != 0) { + + // object to load is a player + + PlayerCharacter awopc = (PlayerCharacter) awo; + + // dont load if invis + + if (player.getSeeInvis() < awopc.getHidden()) + continue; + + lcm = new LoadCharacterMsg(awopc, PlayerCharacter.hideNonAscii()); + players.add(awo); + + // check if in a group with the person being loaded + // and if so set updateGroup flag + + if (GroupManager.getGroup(player) != null + && GroupManager.getGroup(player) == GroupManager.getGroup(awopc)) + + // submit a job as for some reason the client needs a delay + // with group updates + // as it wont update if we do RefreshGroup directly after + // sending the lcm below + + JobScheduler.getInstance().scheduleJob(new RefreshGroupJob(player, awopc), MBServerStatics.LOAD_OBJECT_DELAY); + + } else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0) { + Mob awonpc = (Mob) awo; + + if (!awonpc.isAlive() && (awonpc.isPet() || awonpc.isSiege() || awonpc.isNecroPet() || awonpc.isPlayerGuard())) + continue; + + //removed, interest manager should still load mob corpses + + if (awonpc.despawned == true) + continue; + + awonpc.playerAgroMap.put(player.getObjectUUID(), false); + ((Mob) awonpc).setCombatTarget(null); + lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii()); + + } else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) { + NPC awonpc = (NPC) awo; + lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii()); + } else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0) { + Mob awonpc = (Mob) awo; + + if (!awonpc.isAlive()) + continue; + + awonpc.playerAgroMap.put(player.getObjectUUID(), false); + + if ((awonpc.agentType.equals(Enum.AIAgentType.MOBILE))) + ((Mob) awonpc).setCombatTarget(null); + + lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii()); + } + + addToList.add(awo); + + if (lcm != null) { + Dispatch dispatch = Dispatch.borrow(player, lcm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + + } catch (Exception e) { + Logger.error(awo.getObjectUUID() + " " + e.getMessage()); + } + //Delaying character loading to reduce bandwidth consumption + } + + // send effects for all players being loaded + // do it on a timer otherwise we may get failures as te client needs + // time to process lcm + //Added effects to LoadCharacter Serialization. + //JobScheduler.getInstance().scheduleJob(new LoadEffectsJob(players, origin), MBServerStatics.LOAD_OBJECT_DELAY); + } + + public synchronized void HandleLoadForEnterWorld(PlayerCharacter player) { + + if (player == null) + return; + + ClientConnection origin = player.getClientConnection(); + + if (origin == null) + return; + + // Update loaded upbjects lists + + player.setDirtyLoad(true); + updateStaticList(player, origin); + updateMobileList(player, origin); + + } + + public synchronized void HandleLoadForTeleport(PlayerCharacter playerCharacter) { + + if (playerCharacter == null) + return; + + ClientConnection origin = playerCharacter.getClientConnection(); + + if (origin == null) + return; + + // Update loaded upbjects lists + + playerCharacter.setDirtyLoad(true); + updateStaticList(playerCharacter, origin); + updateMobileList(playerCharacter, origin); + + } + + public static void setObjectDirty(AbstractWorldObject abstractWorldObject) { + + HashSet playerList = WorldGrid.getObjectsInRangePartial(abstractWorldObject.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_PLAYER); + + if (playerList.isEmpty()) + return; + + for (AbstractWorldObject playerObject : playerList) { + + PlayerCharacter playerCharacter = (PlayerCharacter) playerObject; + playerCharacter.setDirtyLoad(true); + } + } +} \ No newline at end of file diff --git a/src/engine/InterestManagement/RealmMap.java b/src/engine/InterestManagement/RealmMap.java new file mode 100644 index 00000000..4c08f6bf --- /dev/null +++ b/src/engine/InterestManagement/RealmMap.java @@ -0,0 +1,122 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.InterestManagement; + +/* This class is the main interface for Magicbane's + * Interest management facilities. + */ + +import engine.Enum; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.TerritoryChangeMessage; +import engine.objects.City; +import engine.objects.PlayerCharacter; +import engine.objects.Realm; +import engine.server.MBServerStatics; +import engine.util.MapLoader; +import org.pmw.tinylog.Logger; + +import java.awt.*; +import java.util.HashMap; + +import static engine.objects.Realm.getRealm; + +public enum RealmMap { + + REALM_MAP; + + // Spatial hashmap. Used for determining which Realm + // a player is currently located within. + + private static final HashMap _rgbToIDMap = new HashMap<>(); + public static int[][] _realmImageMap; + + public static int getRealmIDByColor(Color color) { + + return _rgbToIDMap.getOrDefault(color, 0); + + } + + public static int getRealmIDAtLocation(Vector3fImmutable pos) { + + int xBuckets = (int) ((pos.getX() / MBServerStatics.MAX_WORLD_WIDTH) * MBServerStatics.SPATIAL_HASH_BUCKETSX); + int yBuckets = (int) ((pos.getZ() / MBServerStatics.MAX_WORLD_HEIGHT) * MBServerStatics.SPATIAL_HASH_BUCKETSY); + + if (yBuckets < 0 || yBuckets >= MBServerStatics.SPATIAL_HASH_BUCKETSY + || xBuckets < 0 || xBuckets >= MBServerStatics.SPATIAL_HASH_BUCKETSX) { + Logger.error("Invalid range; Z: " + yBuckets + ", X: " + xBuckets); + return 255; + } + + return RealmMap._realmImageMap[xBuckets][yBuckets]; + } + + public static void addToColorMap(Color color, int realmID) { + _rgbToIDMap.put(color, realmID); + } + + public static Realm getRealmForCity(City city) { + Realm outRealm = null; + outRealm = city.getRealm(); + return outRealm; + } + + public static Realm getRealmAtLocation(Vector3fImmutable worldVector) { + + return getRealm(RealmMap.getRealmIDAtLocation(worldVector)); + + } + + public static void updateRealm(PlayerCharacter player) { + + int realmID = RealmMap.getRealmIDAtLocation(player.getLoc()); + + if (realmID != player.getLastRealmID()) { + player.setLastRealmID(realmID); + Realm realm = Realm.getRealm(realmID); + if (realm != null) { + if (realm.isRuled()) { + City city = realm.getRulingCity(); + if (city != null) { + TerritoryChangeMessage tcm = new TerritoryChangeMessage((PlayerCharacter) realm.getRulingCity().getOwner(), realm); + Dispatch dispatch = Dispatch.borrow(player, tcm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } else { + TerritoryChangeMessage tcm = new TerritoryChangeMessage(null, realm); + Dispatch dispatch = Dispatch.borrow(player, tcm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } + + } else { + TerritoryChangeMessage tcm = new TerritoryChangeMessage(null, realm); + Dispatch dispatch = Dispatch.borrow(player, tcm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } + } + + } + + + } + + public static void loadRealmImageMap() { + + // Build color lookup map for realms from database + + for (Realm realm : Realm._realms.values()) { + RealmMap.addToColorMap(realm.mapColor, realm.realmID); + } + + RealmMap._realmImageMap = MapLoader.loadMap(); + + } + +} diff --git a/src/engine/InterestManagement/WorldGrid.java b/src/engine/InterestManagement/WorldGrid.java new file mode 100644 index 00000000..2eb71a40 --- /dev/null +++ b/src/engine/InterestManagement/WorldGrid.java @@ -0,0 +1,312 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.InterestManagement; + +import engine.Enum.GridObjectType; +import engine.math.FastMath; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.LoadCharacterMsg; +import engine.net.client.msg.LoadStructureMsg; +import engine.net.client.msg.UnloadObjectsMsg; +import engine.objects.*; +import engine.server.MBServerStatics; + +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + + +public class WorldGrid { + + public static ConcurrentHashMap[][] DynamicGridMap; + public static ConcurrentHashMap[][] StaticGridMap; + private static float dynamicBucketScale = 0.00390625f; // 256 bucket size, 1/256 + private static float staticBucketScale = 0.00390625f; + + public static void startLoadJob() { + + Thread loadJobThread; + + + loadJobThread = new Thread(InterestManager.INTERESTMANAGER); + loadJobThread.setName("InterestManager"); + loadJobThread.start(); + } + + public static boolean moveWorldObject(AbstractWorldObject awo, Vector3fImmutable location) { + awo.setLoc(location); + return true; + } + + public static HashSet getInRange(Vector3f loc, double r) { + HashSet outbound = new HashSet<>(); + return outbound; + } + + public static HashSet getObjectsInRangePartial(Vector3fImmutable loc, double r, int mask) { + HashSet outbound = new HashSet<>(); + float scale; + + if ((mask & MBServerStatics.MASK_STATIC) != 0) + scale = WorldGrid.staticBucketScale; + else + scale = WorldGrid.dynamicBucketScale; + int gridX = (int) Math.abs(loc.x * scale); + int gridZ = (int) Math.abs(loc.z * scale); + int bucketSize = (int) (r * scale) + 1; + //start at top left most corner to scan. + int startingX = gridX - bucketSize; + int startingZ = gridZ + bucketSize; + + + int limitX = Math.abs((int) (MBServerStatics.MAX_WORLD_WIDTH * scale)); + int limitZ = Math.abs((int) (MBServerStatics.MAX_WORLD_HEIGHT * scale)); //LimitZ is negative, remember to flip sign. + + if (startingX < 0) + startingX = 0; + + if (startingZ < 0) + startingZ = 0; + + if (startingX > limitX) + startingX = limitX; + + if (startingZ > limitZ) + startingZ = limitZ; + + int endX = startingX + (bucketSize * 2); + int endZ = startingZ - (bucketSize * 2); + + if (endX < 0) + endX = 0; + + if (endZ < 0) + endZ = 0; + + if (endX > limitX) + endX = limitX; + + if (endZ > limitZ) + endZ = limitZ; + + int auditMob = 0; + for (int x = startingX; x <= endX; x++) { + for (int z = startingZ; z >= endZ; z--) { + + ConcurrentHashMap gridMap; + + if ((MBServerStatics.MASK_STATIC & mask) != 0) + gridMap = WorldGrid.StaticGridMap[x][z]; + else + gridMap = WorldGrid.DynamicGridMap[x][z]; + for (AbstractWorldObject gridObject : gridMap.values()) { + if ((gridObject.getObjectTypeMask() & mask) == 0) + continue; + if (gridObject.getLoc().distanceSquared2D(loc) <= FastMath.sqr(r)) + outbound.add(gridObject); + } + } + } + return outbound; + } + + public static HashSet getObjectsInRangePartialNecroPets(Vector3fImmutable loc, double r) { + HashSet outbound = new HashSet<>(); + return outbound; + } + + public static HashSet getObjectsInRangeContains(Vector3fImmutable loc, double r, int mask) { + HashSet outbound = getObjectsInRangePartial(loc, r, mask); + return outbound; + } + + public static HashSet getObjectsInRangePartial(AbstractWorldObject awo, double range, int mask) { + return getObjectsInRangePartial(awo.getLoc(), range, mask); + } + + + public static void InitializeGridObjects() { + + int dynamicWidth = (int) Math.abs(MBServerStatics.MAX_WORLD_WIDTH * WorldGrid.dynamicBucketScale); + int dynamicHeight = (int) Math.abs(MBServerStatics.MAX_WORLD_HEIGHT * WorldGrid.dynamicBucketScale); + + int staticWidth = (int) Math.abs(MBServerStatics.MAX_WORLD_WIDTH * WorldGrid.staticBucketScale); + int staticHeight = (int) Math.abs(MBServerStatics.MAX_WORLD_HEIGHT * WorldGrid.staticBucketScale); + WorldGrid.DynamicGridMap = new ConcurrentHashMap[dynamicWidth + 1][dynamicHeight + 1]; + WorldGrid.StaticGridMap = new ConcurrentHashMap[staticWidth + 1][staticHeight + 1]; + //create new hash maps for each bucket + for (int x = 0; x <= staticWidth; x++) + for (int y = 0; y <= staticHeight; y++) { + WorldGrid.StaticGridMap[x][y] = new ConcurrentHashMap(); + } + + for (int x = 0; x <= dynamicWidth; x++) + for (int y = 0; y <= dynamicHeight; y++) { + WorldGrid.DynamicGridMap[x][y] = new ConcurrentHashMap(); + } + + } + + public static void RemoveWorldObject(AbstractWorldObject gridObject) { + + if (gridObject == null) + return; + AbstractWorldObject.RemoveFromWorldGrid(gridObject); + } + + public static boolean addObject(AbstractWorldObject gridObject, float x, float z) { + + if (gridObject == null) + return false; + + if (x > MBServerStatics.MAX_WORLD_WIDTH) + return false; + + if (z < MBServerStatics.MAX_WORLD_HEIGHT) + return false; + + if (x < 0) + return false; + if (z > 0) + return false; + + int gridX; + int gridZ; + + if (gridObject.getGridObjectType().equals(GridObjectType.STATIC)) { + gridX = Math.abs((int) (x * WorldGrid.staticBucketScale)); + gridZ = Math.abs((int) (z * WorldGrid.staticBucketScale)); + } else { + gridX = Math.abs((int) (x * WorldGrid.dynamicBucketScale)); + gridZ = Math.abs((int) (z * WorldGrid.dynamicBucketScale)); + } + + + WorldGrid.RemoveWorldObject(gridObject); + + return AbstractWorldObject.AddToWorldGrid(gridObject, gridX, gridZ); + + + } + + public static void unloadObject(AbstractWorldObject awo) { + + UnloadObjectsMsg uom = new UnloadObjectsMsg(); + uom.addObject(awo); + DispatchMessage.sendToAllInRange(awo, uom); + } + + public static void loadObject(AbstractWorldObject awo) { + + LoadStructureMsg lsm; + LoadCharacterMsg lcm; + + switch (awo.getObjectType()) { + case Building: + lsm = new LoadStructureMsg(); + lsm.addObject((Building) awo); + DispatchMessage.sendToAllInRange(awo, lsm); + break; + case NPC: + lcm = new LoadCharacterMsg((NPC) awo, false); + DispatchMessage.sendToAllInRange(awo, lcm); + break; + case Mob: + lcm = new LoadCharacterMsg((Mob) awo, false); + DispatchMessage.sendToAllInRange(awo, lcm); + break; + default: + // *** Refactor: Log error? + break; + } + } + + public static void loadObject(AbstractWorldObject awo, ClientConnection origin) { + + LoadStructureMsg lsm; + LoadCharacterMsg lcm; + + switch (awo.getObjectType()) { + + case Building: + lsm = new LoadStructureMsg(); + lsm.addObject((Building) awo); + DispatchMessage.sendToAllInRange(awo, lsm); + break; + case NPC: + lcm = new LoadCharacterMsg((NPC) awo, false); + DispatchMessage.sendToAllInRange(awo, lcm); + break; + case Mob: + lcm = new LoadCharacterMsg((Mob) awo, false); + DispatchMessage.sendToAllInRange(awo, lcm); + break; + case PlayerCharacter: + lcm = new LoadCharacterMsg((PlayerCharacter) awo, false); + DispatchMessage.sendToAllInRange(awo, lcm); + break; + default: + // *** Refactor: Log error? + break; + } + } + + public static void unloadObject(AbstractWorldObject awo, + ClientConnection origin) { + UnloadObjectsMsg uom = new UnloadObjectsMsg(); + uom.addObject(awo); + DispatchMessage.sendToAllInRange(awo, uom); + } + + public static void addObject(AbstractWorldObject awo, PlayerCharacter pc) { + if (pc == null || awo == null) + return; + ClientConnection origin = pc.getClientConnection(); + if (origin == null) + return; + loadObject(awo, origin); + } + + public static void removeObject(AbstractWorldObject awo, PlayerCharacter pc) { + if (pc == null || awo == null) + return; + ClientConnection origin = pc.getClientConnection(); + if (origin == null) + return; + unloadObject(awo, origin); + } + + public static void updateObject(AbstractWorldObject awo, PlayerCharacter pc) { + if (pc == null || awo == null) + return; + ClientConnection origin = pc.getClientConnection(); + if (origin == null) + return; + unloadObject(awo, origin); + loadObject(awo, origin); + } + + public static void updateObject(AbstractWorldObject awo) { + if (awo == null) + return; + unloadObject(awo); + loadObject(awo); + } + + /* + * + */ + public static void removeObject(AbstractWorldObject awo) { + if (awo == null) + return; + unloadObject(awo); + } +} diff --git a/src/engine/core/ControlledRunnable.java b/src/engine/core/ControlledRunnable.java new file mode 100644 index 00000000..0dde32ed --- /dev/null +++ b/src/engine/core/ControlledRunnable.java @@ -0,0 +1,173 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.core; + +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +/** + * + */ +public abstract class ControlledRunnable implements Runnable { + // Runnable tracking + private static final ArrayList runnables = new ArrayList<>(); + private final String threadName; + protected boolean runCmd = false; + protected boolean runStatus = false; + private Thread thisThread; + + /* + * Main loop + */ + + public ControlledRunnable(String threadName) { + super(); + this.threadName = threadName; + ControlledRunnable.runnables.add(this); + } + + public static void shutdownAllRunnables() { + for (ControlledRunnable cr : ControlledRunnable.runnables) { + //Use Direct logging since JobManager is a runnable. + Logger.info("ControlledRunnable", + "Sending Shutdown cmd to: " + cr.threadName); + cr.shutdown(); + } + } + + /** + * This is the method called when ControlledRunnable.thisThread.start() is + * called. + */ + @Override + public void run() { + if (this._preRun() == false) { + return; + } + + this.runStatus = true; + + if (this._Run() == false) { + return; + } + + if (this._postRun() == false) { + return; + } + + this.runStatus = false; + } + + /** + * _preRun() is called prior to the call to _Run(), but after _startup() + * + * @return + */ + protected abstract boolean _preRun(); + + /* + * Control + */ + + /** + * _Run() is called after _startup() and contains should contain the main + * loop. + * + * @return + */ + protected abstract boolean _Run(); + + /** + * _postRun() is called after _Run() exits, not necessarily before + * _shutdown() + * + * @return + */ + protected abstract boolean _postRun(); + + /** + * startup() initializes the internal thread, sets the runCMD to true, and + * calls _startup() prior to starting of the internal Thread. + */ + public void startup() { + + this.thisThread = new Thread(this, this.threadName); + this.runCmd = true; + this._startup(); + this.thisThread.start(); + } + + /** + * This method is called just before ControlledRunnable.thisThread.start() + * is called. + */ + protected abstract void _startup(); + + /** + * This method is called to request a shutdown of the runnable. + */ + public void shutdown() { + this.runCmd = false; + this._shutdown(); + } + + /** + * This method is called just after ControlledRunnable.runCmd is set to + * False. + */ + protected abstract void _shutdown(); + + /* + * Getters n setters + */ + public boolean getRunCmd() { + return runCmd; + } + + public boolean getRunStatus() { + return runStatus; + } + + /* + * Blockers + */ + public void blockTillRunStatus(boolean status) { + while (this.runStatus != status) { + try { + System.out.println("BLOCKING"); + Thread.sleep(25L); + } catch (InterruptedException e) { + Logger.debug(e.getMessage()); + + break; + } + } + } + + /* + * Instance monitoring and tools + */ + + /** + * @return the thisThread + */ + protected Thread getThisThread() { + return thisThread; + } + + /** + * @return the threadName + */ + public String getThreadName() { + return threadName; + } + +} diff --git a/src/engine/db/archive/BaneRecord.java b/src/engine/db/archive/BaneRecord.java new file mode 100644 index 00000000..700c4a2d --- /dev/null +++ b/src/engine/db/archive/BaneRecord.java @@ -0,0 +1,382 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.Bane; +import engine.objects.City; +import engine.workthreads.WarehousePushThread; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.time.LocalDateTime; +import java.util.concurrent.LinkedBlockingQueue; + +import static engine.Enum.RecordEventType; + +public class BaneRecord extends DataRecord { + + private static final LinkedBlockingQueue recordPool = new LinkedBlockingQueue<>(); + private RecordEventType eventType; + private String cityHash; + private String cityName; + private String cityGuildHash; + private String cityNationHash; + private String baneDropperHash; + private String baneGuildHash; + private String baneNationHash; + private DateTime baneLiveTime; + private DateTime baneDropTime; + + private BaneRecord(Bane bane) { + this.recordType = Enum.DataRecordType.BANE; + this.eventType = RecordEventType.PENDING; + } + + public static BaneRecord borrow(Bane bane, RecordEventType eventType) { + BaneRecord baneRecord; + + baneRecord = recordPool.poll(); + + if (baneRecord == null) { + baneRecord = new BaneRecord(bane); + baneRecord.eventType = eventType; + } else { + baneRecord.recordType = Enum.DataRecordType.BANE; + baneRecord.eventType = eventType; + + } + + baneRecord.cityHash = bane.getCity().getHash(); + baneRecord.cityName = bane.getCity().getCityName(); + baneRecord.cityGuildHash = bane.getCity().getGuild().getHash(); + baneRecord.cityNationHash = bane.getCity().getGuild().getNation().getHash(); + + + if (bane.getOwner() == null) { + baneRecord.baneDropperHash = "ERRANT"; + baneRecord.baneGuildHash = "ERRANT"; + baneRecord.baneNationHash = "ERRANT"; + } else { + baneRecord.baneDropperHash = DataWarehouse.hasher.encrypt(bane.getOwner().getObjectUUID()); // getPlayerCharacter didn't check hash first? OMFG + + + baneRecord.baneGuildHash = bane.getOwner().getGuild().getHash(); + baneRecord.baneNationHash = bane.getOwner().getGuild().getNation().getHash(); + + + baneRecord.baneLiveTime = bane.getLiveDate(); + baneRecord.baneDropTime = bane.getPlacementDate(); + } + + + return baneRecord; + } + + public static PreparedStatement buildBanePushStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_banehistory` (`event_number`, `city_id`, `city_name`, `char_id`, `offGuild_id`, `offNat_id`, `defGuild_id`, `defNat_id`, `dropDatetime`, `liveDateTime`, `resolution`) VALUES(?,?,?,?,?,?,?,?,?,?,?)"; + java.util.Date sqlDateTime; + + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("event_number")); + outStatement.setString(2, rs.getString("city_id")); + outStatement.setString(3, rs.getString("city_name")); + outStatement.setString(4, rs.getString("char_id")); + outStatement.setString(5, rs.getString("offGuild_id")); + outStatement.setString(6, rs.getString("offNat_id")); + outStatement.setString(7, rs.getString("defGuild_id")); + outStatement.setString(8, rs.getString("defNat_id")); + + sqlDateTime = rs.getTimestamp("dropDatetime"); + + if (sqlDateTime == null) + outStatement.setNull(9, Types.DATE); + else + outStatement.setTimestamp(9, rs.getTimestamp("dropDatetime")); + + sqlDateTime = rs.getTimestamp("dropDatetime"); + + if (sqlDateTime == null) + outStatement.setNull(10, Types.DATE); + else + outStatement.setTimestamp(10, rs.getTimestamp("liveDateTime")); + + outStatement.setString(11, rs.getString("resolution")); + + return outStatement; + } + + public static PreparedStatement buildBaneQueryStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "SELECT * FROM `warehouse_banehistory` WHERE `event_number` > ?"; + outStatement = connection.prepareStatement(queryString); + outStatement.setInt(1, WarehousePushThread.baneIndex); + return outStatement; + } + + public static DateTime getLastBaneDateTime(City city) { + + DateTime outDateTime = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildDateTimeQueryStatement(connection, city); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + outDateTime = new DateTime(rs.getTimestamp("endDatetime")); + + } + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + return outDateTime; + } + + + private static PreparedStatement buildDateTimeQueryStatement(Connection connection, City city) throws SQLException { + PreparedStatement outStatement; + String queryString = "SELECT `endDatetime` FROM `warehouse_banehistory` WHERE `city_id` = ? ORDER BY `endDatetime` DESC LIMIT 1"; + outStatement = connection.prepareStatement(queryString); + outStatement.setString(1, city.getHash()); + return outStatement; + + } + + public static void updateLiveDate(Bane bane, DateTime dateTime) { + + if (bane == null) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildUpdateLiveDateStatement(connection, bane, dateTime)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + } + + private static PreparedStatement buildUpdateLiveDateStatement(Connection connection, Bane bane, DateTime dateTime) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "UPDATE `warehouse_banehistory` SET `liveDatetime` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'"; + + outStatement = connection.prepareStatement(queryString); + outStatement.setTimestamp(1, new java.sql.Timestamp(dateTime.getMillis())); + outStatement.setString(2, bane.getCity().getHash()); + + return outStatement; + } + + private static PreparedStatement buildUpdateResolutionStatement(Connection connection, Bane bane, RecordEventType eventType) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "UPDATE `warehouse_banehistory` SET `endDatetime` = ?, `resolution` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'"; + + outStatement = connection.prepareStatement(queryString); + outStatement.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now())); + outStatement.setString(2, eventType.name()); + outStatement.setString(3, bane.getCity().getHash()); + + return outStatement; + } + + public static void updateResolution(Bane bane, RecordEventType eventType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildUpdateResolutionStatement(connection, bane, eventType)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + } + + public static String getBaneHistoryString() { + + String outString; + String queryString; + String dividerString; + String newLine = System.getProperty("line.separator"); + outString = "[LUA_BANES() DATA WAREHOUSE]" + newLine; + dividerString = "--------------------------------" + newLine; + queryString = "CALL `baneHistory`()"; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = connection.prepareCall(queryString); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + outString += "Magicbane unresolved banes: " + rs.getInt("PENDING") + '/' + rs.getInt("TOTAL") + newLine; + outString += dividerString; + outString += "Bane Resolution History" + newLine; + outString += dividerString; + + outString += "Destruction: " + rs.getInt("DESTROY") + newLine; + outString += "Capture: " + rs.getInt("CAPTURE") + newLine; + outString += "Defended: " + rs.getInt("DEFEND") + newLine; + } + + } catch (SQLException e) { + e.printStackTrace(); + } + return outString; + } + + public static void updateDirtyRecords() { + + String queryString = "SELECT * FROM `warehouse_banehistory` where `dirty` = 1"; + + // Reset character delta + + WarehousePushThread.baneDelta = 0; + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = localConnection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); // Make this an updatable result set as we'll reset the dirty flag as we go along + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + // Only update the index and dirty flag + // if the remote database update succeeded + + if (updateDirtyRecord(rs) == true) + WarehousePushThread.baneDelta++; + else + continue; + + // Reset the dirty flag in the local database + + rs.updateInt("dirty", 0); + rs.updateRow(); + } + + } catch (SQLException e) { + Logger.error(e.toString()); + } + } + + private static boolean updateDirtyRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = buildUpdateDirtyStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + } + + private static PreparedStatement buildUpdateDirtyStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement; + String queryString = "UPDATE `warehouse_banehistory` SET `liveDateTime` = ?, `endDateTime` = ?, `resolution` = ? WHERE `event_number` = ?"; + java.util.Date sqlDateTime; + + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + sqlDateTime = rs.getTimestamp("liveDateTime"); + + if (sqlDateTime == null) + outStatement.setNull(1, Types.DATE); + else + outStatement.setTimestamp(1, rs.getTimestamp("liveDateTime")); + + sqlDateTime = rs.getTimestamp("endDateTime"); + + if (sqlDateTime == null) + outStatement.setNull(2, Types.DATE); + else + outStatement.setTimestamp(2, rs.getTimestamp("endDateTime")); + + outStatement.setString(3, rs.getString("resolution")); + outStatement.setInt(4, rs.getInt("event_number")); + + return outStatement; + } + + void reset() { + this.cityHash = null; + this.cityGuildHash = null; + this.cityNationHash = null; + this.baneDropperHash = null; + this.baneGuildHash = null; + this.baneNationHash = null; + this.baneLiveTime = null; + } + + public void release() { + this.reset(); + recordPool.add(this); + } + + public void write() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildBaneInsertStatement(connection)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + } + + private PreparedStatement buildBaneInsertStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_banehistory` (`city_id`, `city_name`, `char_id`, `offGuild_id`, `offNat_id`, `defGuild_id`, `defNat_id`, `dropDatetime`, `liveDateTime`, `resolution`) VALUES(?,?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + outStatement.setString(1, this.cityHash); + outStatement.setString(2, this.cityName); + outStatement.setString(3, this.baneDropperHash); + outStatement.setString(4, this.baneGuildHash); + outStatement.setString(5, this.baneNationHash); + outStatement.setString(6, this.cityGuildHash); + outStatement.setString(7, this.cityNationHash); + + if (this.baneDropTime == null) + outStatement.setNull(8, java.sql.Types.DATE); + else + outStatement.setTimestamp(8, new java.sql.Timestamp(this.baneDropTime.getMillis())); + + if (this.baneLiveTime == null) + outStatement.setNull(9, java.sql.Types.DATE); + else + outStatement.setTimestamp(9, new java.sql.Timestamp(this.baneLiveTime.getMillis())); + + outStatement.setString(10, this.eventType.name()); + + + return outStatement; + } +} // END CLASS + diff --git a/src/engine/db/archive/CharacterRecord.java b/src/engine/db/archive/CharacterRecord.java new file mode 100644 index 00000000..7b329d32 --- /dev/null +++ b/src/engine/db/archive/CharacterRecord.java @@ -0,0 +1,284 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.Guild; +import engine.objects.PlayerCharacter; +import engine.workthreads.WarehousePushThread; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.time.LocalDateTime; +import java.util.concurrent.LinkedBlockingQueue; + +/* + * This class warehouses character creation events. It also tracks + * updates to summary kills/death data and their promotion class. + */ +public class CharacterRecord extends DataRecord { + + // Local object pool for class + + private static final LinkedBlockingQueue recordPool = new LinkedBlockingQueue<>(); + + private PlayerCharacter player; + + private CharacterRecord(PlayerCharacter player) { + this.recordType = Enum.DataRecordType.CHARACTER; + this.player = player; + } + + public static CharacterRecord borrow(PlayerCharacter player) { + CharacterRecord characterRecord; + + characterRecord = recordPool.poll(); + + if (characterRecord == null) { + characterRecord = new CharacterRecord(player); + } else { + characterRecord.recordType = Enum.DataRecordType.CHARACTER; + characterRecord.player = player; + + } + + return characterRecord; + } + + private static PreparedStatement buildCharacterInsertStatement(Connection connection, PlayerCharacter player) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_characterhistory` (`char_id`, `char_fname`, `char_lname`, `baseClass`, `race`, `promoteClass`, `startingGuild`, `datetime`) VALUES(?,?,?,?,?,?,?,?)"; + Guild charGuild; + + outStatement = connection.prepareStatement(queryString); + + charGuild = player.getGuild(); + + // Bind character data + + outStatement.setString(1, DataWarehouse.hasher.encrypt(player.getObjectUUID())); + outStatement.setString(2, player.getFirstName()); + outStatement.setString(3, player.getLastName()); + outStatement.setInt(4, player.getBaseClassID()); + outStatement.setInt(5, player.getRaceID()); + outStatement.setInt(6, player.getPromotionClassID()); + outStatement.setString(7, DataWarehouse.hasher.encrypt(charGuild.getObjectUUID())); + outStatement.setTimestamp(8, Timestamp.valueOf(LocalDateTime.now())); + + return outStatement; + } + + public static PreparedStatement buildCharacterPushStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_characterhistory` (`event_number`, `char_id`, `char_fname`, `char_lname`, `baseClass`, `race`, `promoteClass`, `startingGuild`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("event_number")); + outStatement.setString(2, rs.getString("char_id")); + outStatement.setString(3, rs.getString("char_fname")); + outStatement.setString(4, rs.getString("char_lname")); + outStatement.setInt(5, rs.getInt("baseClass")); + outStatement.setInt(6, rs.getInt("race")); + outStatement.setInt(7, rs.getInt("promoteClass")); + outStatement.setString(8, rs.getString("startingGuild")); + outStatement.setTimestamp(9, rs.getTimestamp("datetime")); + return outStatement; + } + + public static PreparedStatement buildCharacterQueryStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "SELECT * FROM `warehouse_characterhistory` WHERE `event_number` > ?"; + outStatement = connection.prepareStatement(queryString); + outStatement.setInt(1, WarehousePushThread.charIndex); + return outStatement; + } + + public static void advanceKillCounter(PlayerCharacter player) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildKillCounterStatement(connection, player)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + } + + private static PreparedStatement buildKillCounterStatement(Connection connection, PlayerCharacter player) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "UPDATE `warehouse_characterhistory` SET `kills` = `kills` +1, `dirty` = 1 WHERE `char_id` = ?"; + + if (player == null) + return outStatement; + + outStatement = connection.prepareStatement(queryString); + outStatement.setString(1, player.getHash()); + + return outStatement; + } + + public static void advanceDeathCounter(PlayerCharacter player) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildDeathCounterStatement(connection, player)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + } + + private static PreparedStatement buildDeathCounterStatement(Connection connection, PlayerCharacter player) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "UPDATE `warehouse_characterhistory` SET `deaths` = `deaths` +1, `dirty` = 1 WHERE `char_id` = ?"; + + if (player == null) + return outStatement; + + outStatement = connection.prepareStatement(queryString); + outStatement.setString(1, player.getHash()); + + return outStatement; + } + + public static void updatePromotionClass(PlayerCharacter player) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildUpdatePromotionStatement(connection, player)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + } + + private static PreparedStatement buildUpdatePromotionStatement(Connection connection, PlayerCharacter player) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "UPDATE `warehouse_characterhistory` SET `promoteClass` = ?, `dirty` = 1 WHERE `char_id` = ?"; + + if (player == null) + return outStatement; + + outStatement = connection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); + outStatement.setInt(1, player.getPromotionClassID()); + outStatement.setString(2, player.getHash()); + + return outStatement; + } + + public static void updateDirtyRecords() { + + String queryString = "SELECT * FROM `warehouse_characterhistory` where `dirty` = 1"; + + // Reset character delta + + WarehousePushThread.charDelta = 0; + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = localConnection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); // Make this an updatable result set as we'll reset the dirty flag as we go along + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + // Only update the index and dirty flag + // if the remote database update succeeded + + if (updateDirtyRecord(rs) == true) + WarehousePushThread.charDelta++; + else + continue; + + // Reset the dirty flag in the local database + + rs.updateInt("dirty", 0); + rs.updateRow(); + } + + } catch (SQLException e) { + Logger.error(e.toString()); + } + } + + private static boolean updateDirtyRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = buildUpdateDirtyStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + } + + private static PreparedStatement buildUpdateDirtyStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement; + String queryString = "UPDATE `warehouse_characterhistory` SET `promoteClass` = ?, `kills` = ?, `deaths` = ? WHERE `char_id` = ?"; + + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("promoteClass")); + outStatement.setInt(2, rs.getInt("kills")); + outStatement.setInt(3, rs.getInt("deaths")); + outStatement.setString(4, rs.getString("char_id")); + + return outStatement; + } + + void reset() { + this.player = null; + } + + public void release() { + this.reset(); + recordPool.add(this); + } + + public void write() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildCharacterInsertStatement(connection, this.player)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error("Error writing character record " + e.toString()); + + } + } + +} + + + + + + + diff --git a/src/engine/db/archive/CityRecord.java b/src/engine/db/archive/CityRecord.java new file mode 100644 index 00000000..099ddf7d --- /dev/null +++ b/src/engine/db/archive/CityRecord.java @@ -0,0 +1,161 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.City; +import engine.workthreads.WarehousePushThread; + +import java.sql.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class CityRecord extends DataRecord { + + private static final LinkedBlockingQueue recordPool = new LinkedBlockingQueue<>(); + private Enum.RecordEventType eventType; + private City city; + private String cityHash; + private String cityGuildHash; + private String cityName; + private String cityMotto; + private float locX; + private float locY; + private String zoneHash; + private java.time.LocalDateTime establishedDatetime; + + private CityRecord(City city) { + this.recordType = Enum.DataRecordType.CITY; + this.city = city; + this.eventType = Enum.RecordEventType.CREATE; + + } + + public static CityRecord borrow(City city, Enum.RecordEventType eventType) { + CityRecord cityRecord; + + cityRecord = recordPool.poll(); + + if (cityRecord == null) { + cityRecord = new CityRecord(city); + cityRecord.eventType = eventType; + } else { + cityRecord.recordType = Enum.DataRecordType.CITY; + cityRecord.eventType = eventType; + cityRecord.city = city; + + } + + if (cityRecord.city.getHash() == null) + cityRecord.city.setHash(DataWarehouse.hasher.encrypt(cityRecord.city.getObjectUUID())); + + cityRecord.cityHash = cityRecord.city.getHash(); + + + cityRecord.cityName = cityRecord.city.getCityName(); + cityRecord.cityMotto = cityRecord.city.getMotto(); + + cityRecord.cityGuildHash = cityRecord.city.getGuild().getHash(); + + cityRecord.locX = cityRecord.city.getTOL().getLoc().x; + cityRecord.locY = -cityRecord.city.getTOL().getLoc().z; // flip sign on 'y' coordinate + + cityRecord.zoneHash = cityRecord.city.getParent().getHash(); + + if (cityRecord.eventType.equals(Enum.RecordEventType.CREATE)) + cityRecord.establishedDatetime = cityRecord.city.established; + else + cityRecord.establishedDatetime = java.time.LocalDateTime.now(); + + return cityRecord; + } + + public static PreparedStatement buildCityPushStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_cityhistory` (`event_number`, `city_id`, `city_name`, `city_motto`, `guild_id`, `loc_x`, `loc_y`, `zone_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?)"; + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("event_number")); + outStatement.setString(2, rs.getString("city_id")); + outStatement.setString(3, rs.getString("city_name")); + outStatement.setString(4, rs.getString("city_motto")); + outStatement.setString(5, rs.getString("guild_id")); + + outStatement.setFloat(6, rs.getFloat("loc_x")); + outStatement.setFloat(7, rs.getFloat("loc_y")); + outStatement.setString(8, rs.getString("zone_id")); + outStatement.setString(9, rs.getString("eventType")); + outStatement.setTimestamp(10, rs.getTimestamp("datetime")); + + return outStatement; + } + + public static PreparedStatement buildCityQueryStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "SELECT * FROM `warehouse_cityhistory` WHERE `event_number` > ?"; + outStatement = connection.prepareStatement(queryString); + outStatement.setInt(1, WarehousePushThread.cityIndex); + return outStatement; + } + + void reset() { + this.city = null; + this.cityHash = null; + this.cityGuildHash = null; + this.cityMotto = null; + this.zoneHash = null; + this.establishedDatetime = null; + + } + + public void release() { + this.reset(); + recordPool.add(this); + } + + public void write() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = this.buildCityInsertStatement(connection)) { + + statement.execute(); + + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private PreparedStatement buildCityInsertStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_cityhistory` (`city_id`, `city_name`, `city_motto`, `guild_id`, `loc_x`, `loc_y`, `zone_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + // Bind character data + + outStatement.setString(1, this.cityHash); + outStatement.setString(2, this.cityName); + outStatement.setString(3, this.cityMotto); + outStatement.setString(4, this.cityGuildHash); + + outStatement.setFloat(5, this.locX); + outStatement.setFloat(6, this.locY); + outStatement.setString(7, this.zoneHash); + outStatement.setString(8, this.eventType.name()); + outStatement.setTimestamp(9, Timestamp.valueOf(this.establishedDatetime)); + + return outStatement; + } +} diff --git a/src/engine/db/archive/DataRecord.java b/src/engine/db/archive/DataRecord.java new file mode 100644 index 00000000..acba35e7 --- /dev/null +++ b/src/engine/db/archive/DataRecord.java @@ -0,0 +1,22 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.Enum; + +class DataRecord { + + public Enum.DataRecordType recordType; + + DataRecord() { + + } + +} diff --git a/src/engine/db/archive/DataWarehouse.java b/src/engine/db/archive/DataWarehouse.java new file mode 100644 index 00000000..d8055409 --- /dev/null +++ b/src/engine/db/archive/DataWarehouse.java @@ -0,0 +1,290 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.util.Hasher; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.LinkedBlockingQueue; + +import static engine.Enum.DataRecordType; + +public class DataWarehouse implements Runnable { + + public static final Hasher hasher = new Hasher("Cthulhu Owns Joo"); + private static final LinkedBlockingQueue recordQueue = new LinkedBlockingQueue<>(); + public static HikariDataSource remoteConnectionPool = null; + + public DataWarehouse() { + + // If WarehousePush is disabled + // then early exit + + if (ConfigManager.MB_WORLD_WAREHOUSE_PUSH.getValue().equalsIgnoreCase("false")) { + Logger.info("Warehouse Remote Connection disabled along with push"); + return; + } + + Logger.info("Configuring remote Database Connection Pool..."); + configureRemoteConnectionPool(); + } + + public static void bootStrap() { + Thread warehousingThread; + warehousingThread = new Thread(new DataWarehouse()); + + warehousingThread.setName("DataWarehouse"); + warehousingThread.setPriority(Thread.NORM_PRIORITY - 1); + warehousingThread.start(); + } + + public static void pushToWarehouse(DataRecord dataRecord) { + + DataWarehouse.recordQueue.add(dataRecord); + } + + public static void writeHash(DataRecordType recordType, int uuid) { + + // Member variable declaration + + Connection connection = null; + PreparedStatement statement = null; + String queryString; + String hashString; + + connection = DbManager.getConnection(); + + if (connection == null) { + Logger.error("Null connection when writing zone hash."); + return; + } + + // Build query string + + switch (recordType) { + case CHARACTER: + queryString = "UPDATE `obj_character` SET hash = ? WHERE `UID` = ?"; + break; + case GUILD: + queryString = "UPDATE `obj_guild` SET hash = ? WHERE `UID` = ?"; + break; + case ZONE: + queryString = "UPDATE `obj_zone` SET hash = ? WHERE `UID` = ?"; + break; + case CITY: + queryString = "UPDATE `obj_city` SET hash = ? WHERE `UID` = ?"; + break; + case REALM: + queryString = "UPDATE `obj_realm` SET hash = ? WHERE `realmID` = ?"; + break; + default: + queryString = null; + break; + } + + hashString = hasher.encrypt(uuid); + + // Write this record to the warehouse + + try { + + statement = connection.prepareStatement(queryString); + + statement.setString(1, hashString); + statement.setLong(2, uuid); + statement.execute(); + } catch (SQLException e) { + Logger.error("Error writing hash for uuid" + uuid + " of type " + recordType.name() + ' ' + e.toString()); + e.printStackTrace(); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + } + + public static boolean recordExists(DataRecordType recordType, int uuid) { + + // Member variable declaration + + Connection connection = null; + PreparedStatement statement = null; + String queryString; + ResultSet resultSet; + + connection = DbManager.getConnection(); + + if (connection == null) { + Logger.error("Null connection during char record lookup"); + return true; // False positive here, so as not to try and write the record twice. + // will refactor out once we write hashes to object tables + } + + // Build query string + + switch (recordType) { + case CHARACTER: + queryString = "SELECT COUNT(*) from warehouse_characterhistory where char_id = ?"; + break; + case GUILD: + queryString = "SELECT COUNT(*) from warehouse_guildhistory where guild_id = ?"; + break; + case CITY: + queryString = "SELECT COUNT(*) from warehouse_cityhistory where city_id = ?"; + break; + case REALM: + queryString = "SELECT COUNT(*) from warehouse_realmhistory where realm_id = ?"; + break; + case BANE: + queryString = "SELECT COUNT(*) from warehouse_banehistory where city_id = ? AND `resolution` = 'PENDING'"; + break; + case ZONE: // Does not really exist but enum acts as a proxy for hash lookup + case MINE: // Does not really exist but enum acts as a proxy for hash lookup + default: + queryString = null; + break; + } + + try { + statement = connection.prepareStatement(queryString); + statement.setString(1, DataWarehouse.hasher.encrypt(uuid)); + resultSet = statement.executeQuery(); + + while (resultSet.next()) { + return resultSet.getInt("COUNT(*)") > 0; + } + + } catch (SQLException e) { + Logger.error("Error in record lookup for " + recordType.name() + " of uuid:" + uuid + e.toString()); + e.printStackTrace(); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + return false; + } + + private static void configureRemoteConnectionPool() { + + HikariConfig config = new HikariConfig(); + + config.setMaximumPoolSize(1); // Only the server talks to remote, so yeah. + config.setJdbcUrl(ConfigManager.MB_WAREHOUSE_ADDR.getValue()); + config.setUsername(ConfigManager.MB_WAREHOUSE_USER.getValue()); + config.setPassword(ConfigManager.MB_WAREHOUSE_PASS.getValue()); + config.addDataSourceProperty("characterEncoding", "utf8"); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "250"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + + remoteConnectionPool = new HikariDataSource(config); // setup the connection pool + + Logger.info("remote warehouse connection configured"); + } + + public void run() { + + // Working variable set + + DataRecord dataRecord; + PvpRecord pvpRecord; + GuildRecord guildRecord; + CharacterRecord characterRecord; + CityRecord cityRecord; + BaneRecord baneRecord; + RealmRecord realmRecord; + MineRecord mineRecord; + + Logger.info("DataWarehouse is running."); + + while (true) { + + dataRecord = null; + pvpRecord = null; + guildRecord = null; + characterRecord = null; + cityRecord = null; + baneRecord = null; + realmRecord = null; + mineRecord = null; + + try { + dataRecord = recordQueue.take(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Write record to appropriate warehousing table + + if (dataRecord != null) { + + switch (dataRecord.recordType) { + case PVP: + pvpRecord = (PvpRecord) dataRecord; + pvpRecord.write(); + pvpRecord.release(); + break; + case CHARACTER: + characterRecord = (CharacterRecord) dataRecord; + characterRecord.write(); + characterRecord.release(); + break; + case GUILD: + guildRecord = (GuildRecord) dataRecord; + guildRecord.write(); + guildRecord.release(); + break; + case CITY: + cityRecord = (CityRecord) dataRecord; + cityRecord.write(); + cityRecord.release(); + break; + case BANE: + baneRecord = (BaneRecord) dataRecord; + baneRecord.write(); + baneRecord.release(); + break; + case REALM: + realmRecord = (RealmRecord) dataRecord; + realmRecord.write(); + realmRecord.release(); + break; + case MINE: + mineRecord = (MineRecord) dataRecord; + mineRecord.write(); + mineRecord.release(); + break; + default: + Logger.error("Unhandled record type"); + break; + + } // end switch + } + } + } + +} diff --git a/src/engine/db/archive/GuildRecord.java b/src/engine/db/archive/GuildRecord.java new file mode 100644 index 00000000..9447fd54 --- /dev/null +++ b/src/engine/db/archive/GuildRecord.java @@ -0,0 +1,211 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.Enum; +import engine.Enum.RecordEventType; +import engine.gameManager.DbManager; +import engine.objects.Guild; +import engine.workthreads.WarehousePushThread; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.HashMap; +import java.util.concurrent.LinkedBlockingQueue; + +public class GuildRecord extends DataRecord { + + private static final LinkedBlockingQueue recordPool = new LinkedBlockingQueue<>(); + public static HashMap GuildRecordCache = null; + public String guildHash; + public int guildID; + private Enum.RecordEventType eventType; + private Guild guild; + private String guildName; + private String charterName; + private String GLHash; + private String guildMotto; + private int bgIcon; + private int bgColour1; + private int bgColour2; + private int fgIcon; + private int fgColour; + private java.time.LocalDateTime eventDatetime; + + private GuildRecord(Guild guild) { + this.recordType = Enum.DataRecordType.GUILD; + this.guild = guild; + this.eventType = Enum.RecordEventType.CREATE; + } + + + public GuildRecord(ResultSet rs) throws SQLException { + super(); + this.eventType = RecordEventType.valueOf(rs.getString("eventType")); + this.guildHash = rs.getString("guild_id"); + this.guildName = rs.getString("guild_name"); + this.charterName = rs.getString("charter"); + GLHash = rs.getString("guild_founder"); + this.guildMotto = rs.getString("guild_motto"); + this.bgIcon = rs.getInt("bgicon"); + this.bgColour1 = rs.getInt("bgcoloura"); + this.bgColour2 = rs.getInt("bgcolourb"); + this.fgIcon = rs.getInt("fgicon"); + this.fgColour = rs.getInt("fgcolour"); + + java.sql.Timestamp eventTimeStamp = rs.getTimestamp("upgradeDate"); + + if (eventTimeStamp != null) + this.eventDatetime = LocalDateTime.ofInstant(eventTimeStamp.toInstant(), ZoneId.systemDefault()); + } + + + public static GuildRecord borrow(Guild guild, Enum.RecordEventType eventType) { + GuildRecord guildRecord; + //add + guildRecord = recordPool.poll(); + + if (guildRecord == null) { + guildRecord = new GuildRecord(guild); + guildRecord.eventType = eventType; + } else { + guildRecord.guild = guild; + guildRecord.recordType = Enum.DataRecordType.GUILD; + guildRecord.eventType = eventType; + + } + + guildRecord.guildHash = guildRecord.guild.getHash(); + guildRecord.guildID = guildRecord.guild.getObjectUUID(); + guildRecord.guildName = guildRecord.guild.getName(); + guildRecord.charterName = Enum.GuildType.getGuildTypeFromInt(guildRecord.guild.getCharter()).getCharterName(); + + guildRecord.GLHash = DataWarehouse.hasher.encrypt(guildRecord.guild.getGuildLeaderUUID()); + + guildRecord.guildMotto = guildRecord.guild.getMotto(); + guildRecord.bgIcon = guildRecord.guild.getBgDesign(); + guildRecord.bgColour1 = guildRecord.guild.getBgc1(); + guildRecord.bgColour2 = guildRecord.guild.getBgc2(); + guildRecord.fgIcon = guildRecord.guild.getSymbol(); + guildRecord.fgColour = guildRecord.guild.getSc(); + + if (guild.getOwnedCity() != null) + guildRecord.eventDatetime = guild.getOwnedCity().established; + else + guildRecord.eventDatetime = LocalDateTime.now(); + + return guildRecord; + } + + public static PreparedStatement buildGuildPushStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_guildhistory` (`event_number`, `guild_id`, `guild_name`, `guild_motto`, `guild_founder`, `charter`, `bgicon`, `bgcoloura`, `bgcolourb`, `fgicon`, `fgcolour`, `eventtype`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("event_number")); + outStatement.setString(2, rs.getString("guild_id")); + outStatement.setString(3, rs.getString("guild_name")); + outStatement.setString(4, rs.getString("guild_motto")); + outStatement.setString(5, rs.getString("guild_founder")); + outStatement.setString(6, rs.getString("charter")); + outStatement.setInt(7, rs.getInt("bgicon")); + outStatement.setInt(8, rs.getInt("bgcoloura")); + outStatement.setInt(9, rs.getInt("bgcolourb")); + outStatement.setInt(10, rs.getInt("fgicon")); + outStatement.setInt(11, rs.getInt("fgcolour")); + outStatement.setString(12, rs.getString("eventtype")); + outStatement.setTimestamp(13, rs.getTimestamp("datetime")); + + return outStatement; + } + + public static PreparedStatement buildGuildQueryStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "SELECT * FROM `warehouse_guildhistory` WHERE `event_number` > ?"; + outStatement = connection.prepareStatement(queryString); + outStatement.setInt(1, WarehousePushThread.guildIndex); + return outStatement; + } + + void reset() { + + this.guild = null; + this.guildHash = null; + this.GLHash = null; + this.guildMotto = null; + this.charterName = null; + this.eventDatetime = null; + } + + public void release() { + this.reset(); + recordPool.add(this); + } + + public void write() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = this.buildGuildInsertStatement(connection)) { + + statement.execute(); + + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + private PreparedStatement buildGuildInsertStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_guildhistory` (`guild_id`, `guild_name`, `guild_motto`, `guild_founder`, `charter`, `bgicon`, `bgcoloura`, `bgcolourb`, `fgicon`, `fgcolour`, `eventtype`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + // Bind character data + + outStatement.setString(1, this.guildHash); + outStatement.setString(2, this.guildName); + outStatement.setString(3, this.guildMotto); + outStatement.setString(4, this.GLHash); + outStatement.setString(5, this.charterName); + + outStatement.setInt(6, this.bgIcon); + outStatement.setInt(7, this.bgColour1); + outStatement.setInt(8, this.bgColour2); + outStatement.setInt(9, this.fgIcon); + outStatement.setInt(10, this.fgColour); + outStatement.setString(11, this.eventType.name()); + outStatement.setTimestamp(12, new java.sql.Timestamp(this.eventDatetime.atZone(ZoneId.systemDefault()) + .toInstant().toEpochMilli())); + + return outStatement; + } + +// public static void InitializeGuildRecords(){ +// GuildRecord.GuildRecordCache = DbManager.GuildQueries.GET_WAREHOUSE_GUILD_HISTORY(); +// } +} + + + + + + + diff --git a/src/engine/db/archive/MineRecord.java b/src/engine/db/archive/MineRecord.java new file mode 100644 index 00000000..25c151bd --- /dev/null +++ b/src/engine/db/archive/MineRecord.java @@ -0,0 +1,165 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.AbstractCharacter; +import engine.objects.Mine; +import engine.objects.PlayerCharacter; +import engine.workthreads.WarehousePushThread; + +import java.sql.*; +import java.time.LocalDateTime; +import java.util.concurrent.LinkedBlockingQueue; + +public class MineRecord extends DataRecord { + + private static final LinkedBlockingQueue recordPool = new LinkedBlockingQueue<>(); + private Enum.RecordEventType eventType; + private String zoneHash; + private String charHash; + private String mineGuildHash; + private String mineNationHash; + private String mineType; + private float locX; + private float locY; + + private MineRecord() { + this.recordType = Enum.DataRecordType.MINE; + this.eventType = Enum.RecordEventType.CAPTURE; + + } + + public static MineRecord borrow(Mine mine, AbstractCharacter character, Enum.RecordEventType eventType) { + + MineRecord mineRecord; + mineRecord = recordPool.poll(); + PlayerCharacter player; + + if (mineRecord == null) { + mineRecord = new MineRecord(); + mineRecord.eventType = eventType; + } else { + mineRecord.recordType = Enum.DataRecordType.MINE; + mineRecord.eventType = eventType; + } + + mineRecord.zoneHash = mine.getParentZone().getHash(); + + if (character.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + player = (PlayerCharacter) character; + mineRecord.charHash = player.getHash(); + } else + mineRecord.charHash = character.getName(); + + DataWarehouse.hasher.encrypt(0); + + if (mine.getOwningGuild() == null) + mineRecord.mineGuildHash = "ERRANT"; + else + mineRecord.mineGuildHash = mine.getOwningGuild().getHash(); + + if (mine.getOwningGuild() == null) + mineRecord.mineNationHash = "ERRANT"; + else + mineRecord.mineNationHash = mine.getOwningGuild().getNation().getHash(); + + mineRecord.locX = mine.getParentZone().getLoc().x; + mineRecord.locY = -mine.getParentZone().getLoc().z; + + mineRecord.mineType = mine.getMineType().name; + + return mineRecord; + } + + public static PreparedStatement buildMinePushStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_minehistory` (`event_number`, `zone_id`, `mine_type`, `char_id`, `mine_guildID`, `mine_nationID`, `loc_x`, `loc_y`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?)"; + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("event_number")); + outStatement.setString(2, rs.getString("zone_id")); + outStatement.setString(3, rs.getString("char_id")); + outStatement.setString(4, rs.getString("mine_type")); + outStatement.setString(5, rs.getString("mine_guildID")); + outStatement.setString(6, rs.getString("mine_nationID")); + + outStatement.setFloat(7, rs.getFloat("loc_x")); + outStatement.setFloat(8, rs.getFloat("loc_y")); + outStatement.setString(9, rs.getString("eventType")); + outStatement.setTimestamp(10, rs.getTimestamp("datetime")); + + return outStatement; + } + + public static PreparedStatement buildMineQueryStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "SELECT * FROM `warehouse_minehistory` WHERE `event_number` > ?"; + outStatement = connection.prepareStatement(queryString); + outStatement.setInt(1, WarehousePushThread.mineIndex); + return outStatement; + } + + void reset() { + this.zoneHash = null; + this.charHash = null; + this.mineGuildHash = null; + this.mineNationHash = null; + this.mineType = null; + this.locX = 0.0f; + this.locY = 0.0f; + + } + + public void release() { + this.reset(); + recordPool.add(this); + } + + public void write() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = this.buildMineInsertStatement(connection)) { + + statement.execute(); + + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private PreparedStatement buildMineInsertStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_minehistory` (`zone_id`, `mine_type`, `char_id`, `mine_guildID`, `mine_nationID`, `loc_x`, `loc_y`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + // Bind character data + + outStatement.setString(1, this.zoneHash); + outStatement.setString(2, this.mineType); + outStatement.setString(3, this.charHash); + outStatement.setString(4, this.mineGuildHash); + outStatement.setString(5, this.mineNationHash); + + outStatement.setFloat(6, this.locX); + outStatement.setFloat(7, this.locY); + outStatement.setString(8, this.eventType.name()); + outStatement.setTimestamp(9, Timestamp.valueOf(LocalDateTime.now())); + + return outStatement; + } +} diff --git a/src/engine/db/archive/PvpRecord.java b/src/engine/db/archive/PvpRecord.java new file mode 100644 index 00000000..bfe3652e --- /dev/null +++ b/src/engine/db/archive/PvpRecord.java @@ -0,0 +1,312 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.Guild; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; +import engine.workthreads.WarehousePushThread; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.time.LocalDateTime; +import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingQueue; + +import static engine.Enum.DataRecordType; +import static engine.Enum.PvpHistoryType; + +public class PvpRecord extends DataRecord { + + private static final LinkedBlockingQueue recordPool = new LinkedBlockingQueue<>(); + + private PlayerCharacter player; + private PlayerCharacter victim; + private Vector3fImmutable location; + private boolean pvpExp; + + private PvpRecord(PlayerCharacter player, PlayerCharacter victim, Vector3fImmutable location, boolean pvpExp) { + this.recordType = DataRecordType.PVP; + this.player = player; + this.victim = victim; + this.location = new Vector3fImmutable(location); + this.pvpExp = pvpExp; + } + + public static PvpRecord borrow(PlayerCharacter player, PlayerCharacter victim, Vector3fImmutable location, boolean pvpExp) { + + PvpRecord pvpRecord; + + pvpRecord = recordPool.poll(); + + if (pvpRecord == null) { + pvpRecord = new PvpRecord(player, victim, location, pvpExp); + } else { + pvpRecord.recordType = DataRecordType.PVP; + pvpRecord.player = player; + pvpRecord.victim = victim; + pvpRecord.location = new Vector3fImmutable(location); + pvpRecord.pvpExp = pvpExp; + } + + return pvpRecord; + } + + private static PreparedStatement buildHistoryStatement(Connection connection, int charUUID, PvpHistoryType historyType) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = ""; + + switch (historyType) { + case KILLS: + queryString = "SELECT DISTINCT `victim_id`, `datetime` FROM warehouse_pvphistory where char_id = ? " + + "ORDER BY `datetime` DESC LIMIT 10"; + break; + case DEATHS: + queryString = "SELECT DISTINCT `char_id`,`datetime` FROM warehouse_pvphistory where `victim_id` = ? " + + "ORDER BY `datetime` DESC LIMIT 10"; + break; + } + + outStatement = connection.prepareStatement(queryString); + outStatement.setString(1, DataWarehouse.hasher.encrypt(charUUID)); + + return outStatement; + } + + public static LinkedList getCharacterPvPHistory(int charUUID, PvpHistoryType historyType) { + + // Member variable declaration + + LinkedList outList = new LinkedList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildHistoryStatement(connection, charUUID, historyType); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + switch (historyType) { + case KILLS: + outList.add((int) DataWarehouse.hasher.decrypt(rs.getString("victim_id"))[0]); + break; + case DEATHS: + outList.add((int) DataWarehouse.hasher.decrypt(rs.getString("char_id"))[0]); + break; + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return outList; + } + + private static PreparedStatement buildLuaHistoryQueryStatement(Connection connection, int charUUID) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "CALL `pvpHistory`(?)"; + + outStatement = connection.prepareStatement(queryString); + outStatement.setString(1, DataWarehouse.hasher.encrypt(charUUID)); + + return outStatement; + } + + public static String getPvpHistoryString(int charUUID) { + + String outString; + String dividerString; + + String newLine = System.getProperty("line.separator"); + + outString = "[LUA_PVP() DATA WAREHOUSE]" + newLine; + dividerString = "--------------------------------" + newLine; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildLuaHistoryQueryStatement(connection, charUUID); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + int killCount; + int deathCount; + float killRatio; + + outString += "Total Magicbane murdered souls: " + rs.getInt("TOTALDEATHS") + newLine; + outString += dividerString; + outString += String.format("%-8s %-8s %-8s %-8s %n", "Period", "Kills", "Deaths", "K/D"); + outString += dividerString; + + killCount = rs.getInt("KILLCOUNT"); + deathCount = rs.getInt("DEATHCOUNT"); + + if (deathCount == 0) + killRatio = (float) killCount; + else + killRatio = (float) killCount / deathCount; + + try { + outString += String.format("%-8s %-8d %-8d %.2f %n", "Total", killCount, deathCount, killRatio); + + killCount = rs.getInt("DAILYKILLS"); + deathCount = rs.getInt("DAILYDEATHS"); + + if (deathCount == 0) + killRatio = (float) killCount; + else + killRatio = (float) killCount / deathCount; + + outString += String.format("%-8s %-8d %-8d %.2f %n", "24hrs", killCount, deathCount, killRatio); + + killCount = rs.getInt("HOURLYKILLS"); + deathCount = rs.getInt("HOURLYDEATHS"); + + if (deathCount == 0) + killRatio = (float) killCount; + else + killRatio = (float) killCount / deathCount; + + outString += String.format("%-8s %-8d %-8d %.2f %n", "1hr", killCount, deathCount, killRatio); + } catch (Exception e) { + Logger.error(e.toString()); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return outString; + } + + public static PreparedStatement buildPvpPushStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_pvphistory` (`event_number`, `char_id`, `char_guild_id`, `char_nation_id`, `char_level`," + + " `victim_id`, `victim_guild_id`, `victim_nation_id`, `victim_level`," + + " `zone_id`, `zone_name`, `loc_x`, `loc_y`, `gave_exp`, `datetime`) " + + " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("event_number")); + outStatement.setString(2, rs.getString("char_id")); + outStatement.setString(3, rs.getString("char_guild_id")); + outStatement.setString(4, rs.getString("char_nation_id")); + outStatement.setInt(5, rs.getInt("char_level")); + + // Bind victim data + + outStatement.setString(6, rs.getString("victim_id")); + outStatement.setString(7, rs.getString("victim_guild_id")); + outStatement.setString(8, rs.getString("victim_nation_id")); + outStatement.setInt(9, rs.getInt("victim_level")); + + outStatement.setString(10, rs.getString("zone_id")); + outStatement.setString(11, rs.getString("zone_name")); + outStatement.setFloat(12, rs.getFloat("loc_x")); + outStatement.setFloat(13, rs.getFloat("loc_y")); + outStatement.setBoolean(14, rs.getBoolean("gave_exp")); + outStatement.setTimestamp(15, rs.getTimestamp("datetime")); + + return outStatement; + } + + public static PreparedStatement buildPvpQueryStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "SELECT * FROM `warehouse_pvphistory` WHERE `event_number` > ?"; + outStatement = connection.prepareStatement(queryString); + outStatement.setInt(1, WarehousePushThread.pvpIndex); + return outStatement; + } + + void reset() { + this.player = null; + this.victim = null; + this.location = Vector3fImmutable.ZERO; + pvpExp = false; + } + + public void release() { + this.reset(); + recordPool.add(this); + } + + private PreparedStatement buildPvPInsertStatement(Connection connection) throws SQLException { + + Guild charGuild; + Guild victimGuild; + Zone zone; + PreparedStatement outStatement = null; + + String queryString = "INSERT INTO `warehouse_pvphistory` (`char_id`, `char_guild_id`, `char_nation_id`, `char_level`," + + " `victim_id`, `victim_guild_id`, `victim_nation_id`, `victim_level`," + + " `zone_id`, `zone_name`, `loc_x`, `loc_y`, `gave_exp`, `datetime`) " + + " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + charGuild = this.player.getGuild(); + victimGuild = this.victim.getGuild(); + + // Use a proxy in the situation where a char guild is null (errant) + + + // Retrieve the zone name where the PvP event occurred + + zone = ZoneManager.findSmallestZone(this.location); + + outStatement.setString(1, DataWarehouse.hasher.encrypt(this.player.getObjectUUID())); + outStatement.setString(2, DataWarehouse.hasher.encrypt(charGuild.getObjectUUID())); + outStatement.setString(3, DataWarehouse.hasher.encrypt(charGuild.getNation().getObjectUUID())); + outStatement.setInt(4, this.player.getLevel()); + + // Bind victim data + + outStatement.setString(5, DataWarehouse.hasher.encrypt(this.victim.getObjectUUID())); + outStatement.setString(6, DataWarehouse.hasher.encrypt(victimGuild.getObjectUUID())); + outStatement.setString(7, DataWarehouse.hasher.encrypt(victimGuild.getNation().getObjectUUID())); + outStatement.setInt(8, this.victim.getLevel()); + + outStatement.setString(9, DataWarehouse.hasher.encrypt(zone.getObjectUUID())); + outStatement.setString(10, zone.getName()); + outStatement.setFloat(11, this.location.getX()); + outStatement.setFloat(12, -this.location.getZ()); // flip sign on 'y' coordinate + outStatement.setBoolean(13, this.pvpExp); + outStatement.setTimestamp(14, Timestamp.valueOf(LocalDateTime.now())); + + return outStatement; + } + + + public void write() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildPvPInsertStatement(connection)) { + + statement.execute(); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + // Warehouse record for this pvp event written if code path reaches here. + // Time to update the respective kill counters. + + CharacterRecord.advanceKillCounter(this.player); + CharacterRecord.advanceDeathCounter(this.victim); + + } +} diff --git a/src/engine/db/archive/RealmRecord.java b/src/engine/db/archive/RealmRecord.java new file mode 100644 index 00000000..b93ed553 --- /dev/null +++ b/src/engine/db/archive/RealmRecord.java @@ -0,0 +1,142 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.archive; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.Realm; +import engine.workthreads.WarehousePushThread; + +import java.sql.*; +import java.time.LocalDateTime; +import java.util.concurrent.LinkedBlockingQueue; + +public class RealmRecord extends DataRecord { + + private static final LinkedBlockingQueue recordPool = new LinkedBlockingQueue<>(); + + private Realm realm; + private Enum.RecordEventType eventType; + private String cityHash; + private String guildHash; + private String charterType; + private LocalDateTime eventDateTime; + + private RealmRecord(Realm realm) { + this.recordType = Enum.DataRecordType.REALM; + this.realm = realm; + this.eventType = Enum.RecordEventType.CAPTURE; + + } + + public static RealmRecord borrow(Realm realm, Enum.RecordEventType eventType) { + RealmRecord realmRecord; + + realmRecord = recordPool.poll(); + + if (realmRecord == null) { + realmRecord = new RealmRecord(realm); + realmRecord.eventType = eventType; + } else { + realmRecord.recordType = Enum.DataRecordType.REALM; + realmRecord.eventType = eventType; + realmRecord.realm = realm; + + } + + realmRecord.cityHash = realm.getRulingCity().getHash(); + realmRecord.guildHash = realm.getRulingCity().getGuild().getHash(); + realmRecord.charterType = Enum.CharterType.getCharterTypeByID(realmRecord.realm.getCharterType()).name(); + + if (realmRecord.eventType.equals(Enum.RecordEventType.CAPTURE)) + realmRecord.eventDateTime = realm.ruledSince; + else + realmRecord.eventDateTime = LocalDateTime.now(); + + return realmRecord; + } + + public static PreparedStatement buildRealmPushStatement(Connection connection, ResultSet rs) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_realmhistory` (`event_number`, `realm_id`, `realm_name`, `charter`, `city_id`, `guild_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?)"; + + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, rs.getInt("event_number")); + outStatement.setString(2, rs.getString("realm_id")); + outStatement.setString(3, rs.getString("realm_name")); + outStatement.setString(4, rs.getString("charter")); + outStatement.setString(5, rs.getString("city_id")); + outStatement.setString(6, rs.getString("guild_id")); + outStatement.setString(7, rs.getString("eventType")); + outStatement.setTimestamp(8, rs.getTimestamp("datetime")); + + return outStatement; + } + + public static PreparedStatement buildRealmQueryStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "SELECT * FROM `warehouse_realmhistory` WHERE `event_number` > ?"; + outStatement = connection.prepareStatement(queryString); + outStatement.setInt(1, WarehousePushThread.realmIndex); + return outStatement; + } + + void reset() { + + this.realm = null; + this.cityHash = null; + this.guildHash = null; + this.eventDateTime = null; + this.charterType = null; + } + + public void release() { + this.reset(); + recordPool.add(this); + } + + private PreparedStatement buildRealmInsertStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "INSERT INTO `warehouse_realmhistory` (`realm_id`, `realm_name`, `charter`, `city_id`, `guild_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?)"; + outStatement = connection.prepareStatement(queryString); + + // Bind Record Data + + outStatement.setString(1, realm.getHash()); + outStatement.setString(2, realm.getRealmName()); + outStatement.setString(3, charterType); + outStatement.setString(4, cityHash); + outStatement.setString(5, guildHash); + outStatement.setString(6, eventType.name()); + outStatement.setTimestamp(7, Timestamp.valueOf(this.eventDateTime)); + + return outStatement; + } + + public void write() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = this.buildRealmInsertStatement(connection)) { + + statement.execute(); + + } catch (SQLException e) { + e.printStackTrace(); + } + + } + +} diff --git a/src/engine/db/handlers/dbAccountHandler.java b/src/engine/db/handlers/dbAccountHandler.java new file mode 100644 index 00000000..f34ebe60 --- /dev/null +++ b/src/engine/db/handlers/dbAccountHandler.java @@ -0,0 +1,273 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.objects.Account; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbAccountHandler extends dbHandlerBase { + + public dbAccountHandler() { + this.localClass = Account.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public Account GET_ACCOUNT(int accountID) { + + Account account; + + if (accountID == 0) + return null; + + account = (Account) DbManager.getFromCache(GameObjectType.Account, accountID); + + if (account != null) + return account; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `obj_account` WHERE `UID`=?")) { + + preparedStatement.setLong(1, accountID); + + ResultSet rs = preparedStatement.executeQuery(); + account = (Account) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + if (account != null) + account.runAfterLoad(); + + return account; + } + + public void WRITE_ADMIN_LOG(String adminName, String logEntry) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO dyn_admin_log(`dateTime`, `charName`, `eventString`)" + + " VALUES (?, ?, ?)")) { + + preparedStatement.setTimestamp(1, new java.sql.Timestamp(System.currentTimeMillis())); + preparedStatement.setString(2, adminName); + preparedStatement.setString(3, logEntry); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public void SET_TRASH(String machineID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO dyn_trash(`machineID`, `count`)" + + " VALUES (?, 1) ON DUPLICATE KEY UPDATE `count` = `count` + 1;")) { + + preparedStatement.setString(1, machineID); + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public ArrayList GET_TRASH_LIST() { + + ArrayList machineList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("select `machineID` from `dyn_trash`")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + machineList.add(rs.getString(1)); + + } catch (SQLException e) { + Logger.error(e); + } + return machineList; + } + + public void DELETE_VAULT_FOR_ACCOUNT(final int accountID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `parent`=? && `type`='item'")) { + + preparedStatement.setLong(1, accountID); + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public ArrayList GET_ALL_CHARS_FOR_MACHINE(String machineID) { + + ArrayList trashList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("select DISTINCT UID from object \n" + + "where parent IN (select AccountID from dyn_login_history " + + " WHERE`machineID`=?)")) { + + preparedStatement.setString(1, machineID); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + PlayerCharacter trashPlayer; + int playerID; + + playerID = rs.getInt(1); + trashPlayer = PlayerCharacter.getPlayerCharacter(playerID); + + if (trashPlayer == null) + continue; + + if (trashPlayer.isDeleted() == false) + trashList.add(trashPlayer); + } + + } catch (SQLException e) { + Logger.error(e); + } + return trashList; + } + + public void CLEAR_TRASH_TABLE() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM dyn_trash")) { + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public void CREATE_SINGLE(String accountName, String password) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL singleAccountCreate(?,?)")) { + + preparedStatement.setString(1, accountName); + preparedStatement.setString(2, password); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public Account GET_ACCOUNT(String uname) { + + Account account = null; + + if (Account.AccountsMap.get(uname) != null) + return this.GET_ACCOUNT(Account.AccountsMap.get(uname)); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `obj_account` WHERE `acct_uname`=?")) { + + preparedStatement.setString(1, uname); + + ResultSet rs = preparedStatement.executeQuery(); + account = (Account) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + if (account != null) { + account.runAfterLoad(); + + if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) + Account.AccountsMap.put(uname, account.getObjectUUID()); + + } + return account; + } + + public void SET_ACCOUNT_LOGIN(final Account acc, String playerName, final String ip, final String machineID) { + + if (acc.getObjectUUID() == 0 || ip == null || ip.length() == 0) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO dyn_login_history(`AccountID`, `accountName`, `characterName`, `ip`, `machineID`, `timeStamp`)" + + " VALUES (?, ?, ?, ?, ?, ?)")) { + + preparedStatement.setInt(1, acc.getObjectUUID()); + preparedStatement.setString(2, acc.getUname()); + preparedStatement.setString(3, playerName); + preparedStatement.setString(4, ip); + preparedStatement.setString(5, machineID); + preparedStatement.setTimestamp(6, new java.sql.Timestamp(System.currentTimeMillis())); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public void updateDatabase(final Account acc) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_account` SET `acct_passwd`=?, " + + " `acct_lastCharUID`=?, `acct_salt`=?, `discordAccount`=?, " + + " status = ? WHERE `UID`=?")) { + + preparedStatement.setString(1, acc.getPasswd()); + preparedStatement.setInt(2, acc.getLastCharIDUsed()); + preparedStatement.setString(3, acc.getSalt()); + preparedStatement.setString(4, acc.discordAccount); + preparedStatement.setString(5, acc.status.name()); + preparedStatement.setInt(6, acc.getObjectUUID()); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public void INVALIDATE_LOGIN_CACHE(long accountUID, String objectType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT IGNORE INTO login_cachelist (`UID`, `type`) VALUES(?,?);")) { + + preparedStatement.setLong(1, accountUID); + preparedStatement.setString(2, objectType); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + +} diff --git a/src/engine/db/handlers/dbBaneHandler.java b/src/engine/db/handlers/dbBaneHandler.java new file mode 100644 index 00000000..904f0cdd --- /dev/null +++ b/src/engine/db/handlers/dbBaneHandler.java @@ -0,0 +1,110 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Bane; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.PlayerCharacter; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class dbBaneHandler extends dbHandlerBase { + + public dbBaneHandler() { + + } + + public boolean CREATE_BANE(City city, PlayerCharacter owner, Building stone) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_banes` (`cityUUID`, `ownerUUID`, `stoneUUID`, `placementDate`) VALUES(?,?,?,?)")) { + + preparedStatement.setLong(1, city.getObjectUUID()); + preparedStatement.setLong(2, owner.getObjectUUID()); + preparedStatement.setLong(3, stone.getObjectUUID()); + preparedStatement.setTimestamp(4, new java.sql.Timestamp(System.currentTimeMillis())); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + return true; + } + + public Bane LOAD_BANE(int cityUUID) { + + Bane bane = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * from dyn_banes WHERE `dyn_banes`.`cityUUID` = ?")) { + + preparedStatement.setLong(1, cityUUID); + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + bane = new Bane(rs); + Bane.addBane(bane); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return bane; + } + + + public boolean SET_BANE_TIME(DateTime toSet, int cityUUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_banes` SET `liveDate`=? WHERE `cityUUID`=?")) { + + preparedStatement.setTimestamp(1, new java.sql.Timestamp(toSet.getMillis())); + preparedStatement.setLong(2, cityUUID); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + return true; + } + + public boolean REMOVE_BANE(Bane bane) { + + if (bane == null) + return false; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_banes` WHERE `cityUUID` = ?")) { + + preparedStatement.setLong(1, bane.getCity().getObjectUUID()); + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + return true; + } +} diff --git a/src/engine/db/handlers/dbBaseClassHandler.java b/src/engine/db/handlers/dbBaseClassHandler.java new file mode 100644 index 00000000..93f144b5 --- /dev/null +++ b/src/engine/db/handlers/dbBaseClassHandler.java @@ -0,0 +1,91 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.objects.BaseClass; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbBaseClassHandler extends dbHandlerBase { + + public dbBaseClassHandler() { + this.localClass = BaseClass.class; + this.localObjectType = Enum.GameObjectType.BaseClass; + } + + public BaseClass GET_BASE_CLASS(final int id) { + + if (id == 0) + return null; + + BaseClass baseClass = (BaseClass) DbManager.getFromCache(GameObjectType.BaseClass, id); + + if (baseClass != null) + return baseClass; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `obj_account` WHERE `UID`=?")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + baseClass = (BaseClass) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return baseClass; + } + + public ArrayList GET_BASECLASS_FOR_RACE(final int id) { + + ArrayList baseClasses = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT b.* FROM `static_rune_baseclass` b, `static_rune_racebaseclass` r WHERE b.`ID` = r.`BaseClassID` && r.`RaceID` = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + baseClasses = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return baseClasses; + } + + public ArrayList GET_ALL_BASE_CLASSES() { + + ArrayList baseClasses = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_baseclass`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + baseClasses = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return baseClasses; + } +} diff --git a/src/engine/db/handlers/dbBlueprintHandler.java b/src/engine/db/handlers/dbBlueprintHandler.java new file mode 100644 index 00000000..46d36ef7 --- /dev/null +++ b/src/engine/db/handlers/dbBlueprintHandler.java @@ -0,0 +1,85 @@ +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Blueprint; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + +public class dbBlueprintHandler extends dbHandlerBase { + + public dbBlueprintHandler() { + + } + + public HashMap LOAD_ALL_DOOR_NUMBERS() { + + HashMap doorInfo; + doorInfo = new HashMap<>(); + + int doorUUID; + int doorNum; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_building_doors ORDER BY doorMeshUUID ASC")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + doorUUID = rs.getInt("doorMeshUUID"); + doorNum = rs.getInt("doorNumber"); + doorInfo.put(doorUUID, doorNum); + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + doorInfo.size()); + return doorInfo; + } + + public HashMap LOAD_ALL_BLUEPRINTS() { + + HashMap blueprints; + Blueprint thisBlueprint; + + blueprints = new HashMap<>(); + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_building_blueprint")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + thisBlueprint = new Blueprint(rs); + + blueprints.put(thisBlueprint.getBlueprintUUID(), thisBlueprint); + + // load mesh cache + Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(-1), thisBlueprint); + Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(0), thisBlueprint); + Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(1), thisBlueprint); + Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(3), thisBlueprint); + Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(7), thisBlueprint); + + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + blueprints.size()); + return blueprints; + } +} diff --git a/src/engine/db/handlers/dbBoonHandler.java b/src/engine/db/handlers/dbBoonHandler.java new file mode 100644 index 00000000..3c16ca82 --- /dev/null +++ b/src/engine/db/handlers/dbBoonHandler.java @@ -0,0 +1,51 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Boon; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbBoonHandler extends dbHandlerBase { + + public dbBoonHandler() { + } + + + public ArrayList GET_BOON_AMOUNTS_FOR_ITEMBASE(int itemBaseUUID) { + + ArrayList boons = new ArrayList<>(); + Boon thisBoon; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_item_boons` WHERE `itemBaseID` = ?")) { + + preparedStatement.setInt(1, itemBaseUUID); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + thisBoon = new Boon(rs); + boons.add(thisBoon); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return boons; + } +} diff --git a/src/engine/db/handlers/dbBuildingHandler.java b/src/engine/db/handlers/dbBuildingHandler.java new file mode 100644 index 00000000..449def09 --- /dev/null +++ b/src/engine/db/handlers/dbBuildingHandler.java @@ -0,0 +1,855 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.Enum.DbObjectType; +import engine.Enum.ProtectionState; +import engine.Enum.TaxType; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; + +public class dbBuildingHandler extends dbHandlerBase { + + public dbBuildingHandler() { + this.localClass = Building.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public Building CREATE_BUILDING(int parentZoneID, int OwnerUUID, String name, int meshUUID, + Vector3fImmutable location, float meshScale, int currentHP, + ProtectionState protectionState, int currentGold, int rank, + DateTime upgradeDate, int blueprintUUID, float w, float rotY) { + + Building building = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `building_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?);")) { + + preparedStatement.setInt(1, parentZoneID); + preparedStatement.setInt(2, OwnerUUID); + preparedStatement.setString(3, name); + preparedStatement.setInt(4, meshUUID); + preparedStatement.setFloat(5, location.x); + preparedStatement.setFloat(6, location.y); + preparedStatement.setFloat(7, location.z); + preparedStatement.setFloat(8, meshScale); + preparedStatement.setInt(9, currentHP); + preparedStatement.setString(10, protectionState.name()); + preparedStatement.setInt(11, currentGold); + preparedStatement.setInt(12, rank); + + if (upgradeDate != null) + preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis())); + else + preparedStatement.setNull(13, java.sql.Types.DATE); + + preparedStatement.setInt(14, blueprintUUID); + preparedStatement.setFloat(15, w); + preparedStatement.setFloat(16, rotY); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + int objectUUID = (int) rs.getLong("UID"); + + if (objectUUID > 0) + building = GET_BUILDINGBYUUID(objectUUID); + } + + } catch (SQLException e) { + throw new RuntimeException(e); + } + + return building; + } + + public boolean DELETE_FROM_DATABASE(final Building b) { + + return removeFromBuildings(b); + } + + public ArrayList GET_ALL_BUILDINGS_FOR_ZONE(Zone zone) { + + ArrayList buildings = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_building`.*, `object`.`parent` FROM `object` INNER JOIN `obj_building` ON `obj_building`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;")) { + + preparedStatement.setLong(1, zone.getObjectUUID()); + + ResultSet rs = preparedStatement.executeQuery(); + buildings = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + + return buildings; + } + + public Building GET_BUILDINGBYUUID(int uuid) { + + if (uuid == 0) + return null; + + Building building = (Building) DbManager.getFromCache(Enum.GameObjectType.Building, uuid); + + if (building != null) + return building; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_building`.*, `object`.`parent` FROM `object` INNER JOIN `obj_building` ON `obj_building`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;")) { + + preparedStatement.setLong(1, uuid); + + ResultSet rs = preparedStatement.executeQuery(); + building = (Building) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return building; + } + + public Building GET_BUILDING_BY_MESH(final int meshID) { + + Building building = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL building_GETBYMESH(?)")) { + + preparedStatement.setInt(1, meshID); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + building = new Building(rs); + + } catch (SQLException e) { + Logger.error("Building", e); + } + + return building; + } + + public String SET_PROPERTY(final Building b, String name, Object new_value) { + + String result = ""; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL building_SETPROP(?,?,?)")) { + + preparedStatement.setLong(1, b.getObjectUUID()); + preparedStatement.setString(2, name); + preparedStatement.setString(3, String.valueOf(new_value)); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + result = rs.getString("result"); + + } catch (SQLException e) { + Logger.error(e); + } + + return result; + } + + public int MOVE_BUILDING(long buildingID, long parentID, float locX, float locY, float locZ) { + + int rowCount; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` INNER JOIN `obj_building` On `object`.`UID` = `obj_building`.`UID` SET `object`.`parent`=?, `obj_building`.`locationX`=?, `obj_building`.`locationY`=?, `obj_building`.`locationZ`=? WHERE `obj_building`.`UID`=?;")) { + + preparedStatement.setLong(1, parentID); + preparedStatement.setFloat(2, locX); + preparedStatement.setFloat(3, locY); + preparedStatement.setFloat(4, locZ); + preparedStatement.setLong(5, buildingID); + + rowCount = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + return 0; + } + + return rowCount; + } + + private boolean removeFromBuildings(final Building b) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) { + + preparedStatement.setLong(1, b.getObjectUUID()); + preparedStatement.execute(); + + return true; + } catch (SQLException e) { + Logger.error(e); + } + + return false; + } + + public boolean CHANGE_NAME(Building b, String newName) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `name`=? WHERE `UID`=?")) { + + preparedStatement.setString(1, newName); + preparedStatement.setLong(2, b.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_RESERVE(Building b, int reserveAmount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `reserve`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, reserveAmount); + preparedStatement.setLong(2, b.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + + return false; + } + + //CAS update to rank + public boolean CHANGE_RANK(final long buildingID, int newRank) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `rank`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, newRank); + preparedStatement.setLong(2, buildingID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_BUILDING_HEALTH(final long buildingID, int NewHealth) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `currentHP`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, NewHealth); + preparedStatement.setLong(2, buildingID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_BUILDING_ALTITUDE(final long buildingID, float newAltitude) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `locationY`=? WHERE `UID`=?")) { + + preparedStatement.setFloat(1, newAltitude); + preparedStatement.setLong(2, buildingID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_PROTECTIONSTATE(final long buildingUUID, ProtectionState protectionState) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `protectionState`=? WHERE `UID`=?")) { + + preparedStatement.setString(1, protectionState.name()); + preparedStatement.setLong(2, buildingUUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_DOOR_LOCK(final int buildingUUID, int doorFlags) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET doorState = ? WHERE UID = ?")) { + + preparedStatement.setInt(1, doorFlags); + preparedStatement.setInt(2, buildingUUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean ADD_TO_FRIENDS_LIST(final long buildingID, final long friendID, final long guildID, final int friendType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_building_friends` (`buildingUID`, `playerUID`,`guildUID`, `friendType`) VALUES (?,?,?,?)")) { + + preparedStatement.setLong(1, buildingID); + preparedStatement.setLong(2, friendID); + preparedStatement.setLong(3, guildID); + preparedStatement.setInt(4, friendType); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean REMOVE_FROM_FRIENDS_LIST(final long buildingID, long friendID, long guildID, int friendType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_friends` WHERE `buildingUID`=? AND `playerUID`=? AND `guildUID` =? AND `friendType` = ?")) { + + preparedStatement.setLong(1, buildingID); + preparedStatement.setLong(2, friendID); + preparedStatement.setLong(3, guildID); + preparedStatement.setInt(4, friendType); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean REMOVE_FROM_CONDEMNED_LIST(final long buildingID, long friendID, long guildID, int friendType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_condemned` WHERE `buildingUID`=? AND `playerUID`=? AND `guildUID` =? AND `friendType` = ?")) { + + preparedStatement.setLong(1, buildingID); + preparedStatement.setLong(2, friendID); + preparedStatement.setLong(3, guildID); + preparedStatement.setInt(4, friendType); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public void CLEAR_FRIENDS_LIST(final long buildingID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_friends` WHERE `buildingUID`=?")) { + + preparedStatement.setLong(1, buildingID); + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + + } + + public void CLEAR_CONDEMNED_LIST(final long buildingID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_condemned` WHERE `buildingUID`=?")) { + + preparedStatement.setLong(1, buildingID); + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + + } + + public boolean CLEAR_PATROL(final long buildingID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_patrol_points` WHERE `buildingUID`=?")) { + + preparedStatement.setLong(1, buildingID); + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public void LOAD_ALL_FRIENDS_FOR_BUILDING(Building building) { + + if (building == null) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_building_friends` WHERE `buildingUID` = ?")) { + + preparedStatement.setInt(1, building.getObjectUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + BuildingFriends friend = new BuildingFriends(rs); + switch (friend.getFriendType()) { + case 7: + building.getFriends().put(friend.getPlayerUID(), friend); + break; + case 8: + case 9: + building.getFriends().put(friend.getGuildUID(), friend); + break; + } + } + + } catch (SQLException e) { + Logger.error(e); + } + + } + + public void LOAD_ALL_CONDEMNED_FOR_BUILDING(Building building) { + + if (building == null) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_building_condemned` WHERE `buildingUID` = ?")) { + + preparedStatement.setInt(1, building.getObjectUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + Condemned condemned = new Condemned(rs); + switch (condemned.getFriendType()) { + case 2: + building.getCondemned().put(condemned.getPlayerUID(), condemned); + break; + case 4: + case 5: + building.getCondemned().put(condemned.getGuildUID(), condemned); + break; + } + } + + } catch (SQLException e) { + Logger.error(e); + } + } + + public ArrayList LOAD_PATROL_POINTS(Building building) { + + if (building == null) + return null; + + ArrayList patrolPoints = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_building_patrol_points` WHERE `buildingUID` = ?")) { + + preparedStatement.setInt(1, building.getObjectUUID()); + + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + float x1 = rs.getFloat("patrolX"); + float y1 = rs.getFloat("patrolY"); + float z1 = rs.getFloat("patrolZ"); + Vector3fImmutable patrolPoint = new Vector3fImmutable(x1, y1, z1); + patrolPoints.add(patrolPoint); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return patrolPoints; + + } + + public boolean ADD_TO_CONDEMNLIST(final long parentUID, final long playerUID, final long guildID, final int friendType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_building_condemned` (`buildingUID`, `playerUID`,`guildUID`, `friendType`) VALUES (?,?,?,?)")) { + + preparedStatement.setLong(1, parentUID); + preparedStatement.setLong(2, playerUID); + preparedStatement.setLong(3, guildID); + preparedStatement.setInt(4, friendType); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean ADD_TO_PATROL(final long parentUID, final Vector3fImmutable patrolPoint) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_building_patrol_points` (`buildingUID`, `patrolX`,`patrolY`, `patrolZ`) VALUES (?,?,?,?)")) { + + preparedStatement.setLong(1, parentUID); + preparedStatement.setFloat(2, (int) patrolPoint.x); + preparedStatement.setFloat(3, (int) patrolPoint.y); + preparedStatement.setFloat(4, (int) patrolPoint.z); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public HashMap> LOAD_BUILDING_REGIONS() { + + HashMap> regionList = new HashMap<>(); + BuildingRegions buildingRegions; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_building_regions")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + buildingRegions = new BuildingRegions(rs); + + if (regionList.get(buildingRegions.getBuildingID()) == null) { + ArrayList regionsList = new ArrayList<>(); + regionsList.add(buildingRegions); + regionList.put(buildingRegions.getBuildingID(), regionsList); + } else { + ArrayList regionsList = regionList.get(buildingRegions.getBuildingID()); + regionsList.add(buildingRegions); + regionList.put(buildingRegions.getBuildingID(), regionsList); + } + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + regionList.size()); + return regionList; + } + + public HashMap LOAD_MESH_BOUNDS() { + + HashMap meshBoundsMap = new HashMap<>(); + MeshBounds meshBounds; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_mesh_bounds")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + recordsRead++; + meshBounds = new MeshBounds(rs); + meshBoundsMap.put(meshBounds.meshID, meshBounds); + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + meshBoundsMap.size()); + return meshBoundsMap; + } + + public HashMap> LOAD_ALL_STATIC_COLLIDERS() { + + HashMap> colliders = new HashMap<>(); + StaticColliders thisColliders; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_building_colliders")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + thisColliders = new StaticColliders(rs); + + if (colliders.get(thisColliders.getMeshID()) == null) { + ArrayList colliderList = new ArrayList<>(); + colliderList.add(thisColliders); + colliders.put(thisColliders.getMeshID(), colliderList); + } else { + ArrayList colliderList = colliders.get(thisColliders.getMeshID()); + colliderList.add(thisColliders); + colliders.put(thisColliders.getMeshID(), colliderList); + } + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + colliders.size()); + return colliders; + } + + // This public class inserted here as it's a generic utility function + // with no other good place for it. If you find a good home for it + // feel free to move it. - + + public final DbObjectType GET_UID_ENUM(long object_UID) { + + DbObjectType objectType = DbObjectType.INVALID; + String typeString = "INVALID"; + + if (object_UID == 0) + return objectType; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `type` FROM `object` WHERE `object`.`UID` = ? LIMIT 1;")) { + + preparedStatement.setLong(1, object_UID); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + typeString = rs.getString("type").toUpperCase(); + objectType = DbObjectType.valueOf(typeString); + } + + } catch (SQLException e) { + Logger.error(e); + return DbObjectType.INVALID; + } + + return objectType; + } + + public boolean updateBuildingRank(final Building b, int Rank) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `rank`=?," + + "`upgradeDate`=?, `meshUUID`=?, `currentHP`=? " + + "WHERE `UID` = ?")) { + + preparedStatement.setInt(1, Rank); + preparedStatement.setNull(2, java.sql.Types.DATE); + preparedStatement.setInt(3, b.getBlueprint().getMeshForRank(Rank)); + preparedStatement.setInt(4, b.getBlueprint().getMaxHealth(Rank)); + preparedStatement.setInt(5, b.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateReverseKOS(final Building b, boolean reverse) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `reverseKOS`=? WHERE `UID` = ?")) { + + preparedStatement.setBoolean(1, reverse); + preparedStatement.setInt(2, b.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateActiveCondemn(final Condemned condemn, boolean active) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_building_condemned` SET `active`=? " + + "WHERE`buildingUID` = ? AND `playerUID` = ? AND `guildUID` = ? AND `friendType` = ?")) { + + preparedStatement.setBoolean(1, active); + preparedStatement.setInt(2, condemn.getParent()); + preparedStatement.setInt(3, condemn.getPlayerUID()); + preparedStatement.setInt(4, condemn.getGuildUID()); + preparedStatement.setInt(5, condemn.getFriendType()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateBuildingOwner(final Building building, int ownerUUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `ownerUUID`=? " + + " WHERE `UID` = ?")) { + + preparedStatement.setInt(1, ownerUUID); + preparedStatement.setInt(2, building.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateBuildingUpgradeTime(LocalDateTime upgradeDateTime, Building toUpgrade, int costToUpgrade) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET upgradeDate=?, currentGold=? " + + "WHERE UID = ?")) { + + if (upgradeDateTime == null) + preparedStatement.setNull(1, java.sql.Types.DATE); + else + preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.atZone(ZoneId.systemDefault()) + .toInstant().toEpochMilli())); + + preparedStatement.setInt(2, toUpgrade.getStrongboxValue() - costToUpgrade); + preparedStatement.setInt(3, toUpgrade.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateMaintDate(Building building) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET maintDate=? " + + "WHERE UID = ?")) { + + if (building.maintDateTime == null) + preparedStatement.setNull(1, java.sql.Types.DATE); + else + preparedStatement.setTimestamp(1, new java.sql.Timestamp(building.maintDateTime.atZone(ZoneId.systemDefault()) + .toInstant().toEpochMilli())); + preparedStatement.setInt(2, building.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean addTaxes(Building building, TaxType taxType, int amount, boolean enforceKOS) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxType=?, taxAmount = ?, enforceKOS = ? " + + "WHERE UID = ?")) { + + preparedStatement.setString(1, taxType.name()); + preparedStatement.setInt(2, amount); + preparedStatement.setBoolean(3, enforceKOS); + preparedStatement.setInt(4, building.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean removeTaxes(Building building) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxType=?, taxAmount = ?, enforceKOS = ?, taxDate = ? " + + "WHERE UID = ?")) { + + preparedStatement.setString(1, TaxType.NONE.name()); + preparedStatement.setInt(2, 0); + preparedStatement.setBoolean(3, false); + preparedStatement.setNull(4, java.sql.Types.DATE); + preparedStatement.setInt(5, building.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean acceptTaxes(Building building) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxDate=? " + + "WHERE UID = ?")) { + + preparedStatement.setTimestamp(1, new java.sql.Timestamp(DateTime.now().plusDays(7).getMillis())); + preparedStatement.setInt(2, building.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + +} diff --git a/src/engine/db/handlers/dbBuildingLocationHandler.java b/src/engine/db/handlers/dbBuildingLocationHandler.java new file mode 100644 index 00000000..7a3bbd9e --- /dev/null +++ b/src/engine/db/handlers/dbBuildingLocationHandler.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.BuildingLocation; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbBuildingLocationHandler extends dbHandlerBase { + + public dbBuildingLocationHandler() { + this.localClass = BuildingLocation.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList LOAD_BUILDING_LOCATIONS() { + + ArrayList buildingLocations = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("select `ID`, `BuildingID`, `type`, `slot`, `unknown`, `locX`, `locY`, `locZ`, `w`, `rotX`, `rotY`, `rotZ` from static_building_location " + + "where type = 6 or type = 8 " + + "GROUP BY buildingID, slot " + + "ORDER BY buildingID, slot ASC;")) { + + ResultSet rs = preparedStatement.executeQuery(); + buildingLocations = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + return buildingLocations; + } + + return buildingLocations; + } +} diff --git a/src/engine/db/handlers/dbCSSessionHandler.java b/src/engine/db/handlers/dbCSSessionHandler.java new file mode 100644 index 00000000..5516c733 --- /dev/null +++ b/src/engine/db/handlers/dbCSSessionHandler.java @@ -0,0 +1,114 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Account; +import engine.objects.PlayerCharacter; +import engine.session.CSSession; +import engine.util.StringUtils; +import org.pmw.tinylog.Logger; + +import java.net.InetAddress; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class dbCSSessionHandler extends dbHandlerBase { + + public dbCSSessionHandler() { + this.localClass = CSSession.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public boolean ADD_CSSESSION(String secKey, Account acc, InetAddress inet, String machineID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_session` (`secretKey`, `accountID`, `discordAccount`, `sessionIP`, machineID) VALUES (?,?,?,INET_ATON(?),?)")) { + + preparedStatement.setString(1, secKey); + preparedStatement.setLong(2, acc.getObjectUUID()); + preparedStatement.setString(3, acc.discordAccount); + preparedStatement.setString(4, StringUtils.InetAddressToClientString(inet)); + preparedStatement.setString(5, machineID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean DELETE_UNUSED_CSSESSION(String secKey) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_session` WHERE `secretKey`=? && `characterID` IS NULL")) { + + preparedStatement.setString(1, secKey); + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean DELETE_CSSESSION(String secKey) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_session` WHERE `secretKey`=?")) { + + preparedStatement.setString(1, secKey); + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_CSSESSION(String secKey, int charID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_session` SET `characterID`=? WHERE `secretKey`=?")) { + + preparedStatement.setInt(1, charID); + preparedStatement.setString(2, secKey); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public CSSession GET_CSSESSION(String secKey) { + + CSSession css = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `accountID`, `characterID`, `machineID` FROM `dyn_session` WHERE `secretKey`=?")) { + + preparedStatement.setString(1, secKey); + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + css = new CSSession(secKey, DbManager.AccountQueries.GET_ACCOUNT(rs.getInt("accountID")), PlayerCharacter.getPlayerCharacter(rs.getInt("characterID")), rs.getString("machineID")); + + } catch (SQLException e) { + Logger.error(e); + } + + return css; + } +} diff --git a/src/engine/db/handlers/dbCharacterPowerHandler.java b/src/engine/db/handlers/dbCharacterPowerHandler.java new file mode 100644 index 00000000..f16f67fb --- /dev/null +++ b/src/engine/db/handlers/dbCharacterPowerHandler.java @@ -0,0 +1,169 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.CharacterPower; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.util.concurrent.ConcurrentHashMap; + +public class dbCharacterPowerHandler extends dbHandlerBase { + + public dbCharacterPowerHandler() { + this.localClass = CharacterPower.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public CharacterPower ADD_CHARACTER_POWER(CharacterPower toAdd) { + + CharacterPower characterPower = null; + + if (CharacterPower.getOwner(toAdd) == null || toAdd.getPower() == null) { + Logger.error("dbCharacterSkillHandler.ADD_Power", toAdd.getObjectUUID() + " missing owner or powersBase"); + return null; + } + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_power` (`CharacterID`, `powersBaseToken`, `trains`) VALUES (?, ?, ?);", Statement.RETURN_GENERATED_KEYS)) { + + preparedStatement.setLong(1, CharacterPower.getOwner(toAdd).getObjectUUID()); + preparedStatement.setInt(2, toAdd.getPower().getToken()); + preparedStatement.setInt(3, toAdd.getTrains()); + + preparedStatement.executeUpdate(); + ResultSet rs = preparedStatement.getGeneratedKeys(); + + if (rs.next()) + characterPower = GET_CHARACTER_POWER(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + return null; + } + return characterPower; + } + + public int DELETE_CHARACTER_POWER(final int objectUUID) { + + int rowCount = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_character_power` WHERE `UID` = ?")) { + + preparedStatement.setLong(1, objectUUID); + rowCount = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + } + + return rowCount; + } + + public CharacterPower GET_CHARACTER_POWER(int objectUUID) { + + CharacterPower characterPower = (CharacterPower) DbManager.getFromCache(Enum.GameObjectType.CharacterPower, objectUUID); + + if (characterPower != null) + return characterPower; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_character_power` WHERE `UID` = ?")) { + + preparedStatement.setLong(1, objectUUID); + ResultSet rs = preparedStatement.executeQuery(); + characterPower = (CharacterPower) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return characterPower; + } + + public ConcurrentHashMap GET_POWERS_FOR_CHARACTER(PlayerCharacter pc) { + + ConcurrentHashMap powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + int objectUUID = pc.getObjectUUID(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_character_power` WHERE CharacterID = ?")) { + + preparedStatement.setLong(1, objectUUID); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + CharacterPower cp = new CharacterPower(rs, pc); + if (cp.getPower() != null) + powers.put(cp.getPower().getToken(), cp); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return powers; + } + + public void UPDATE_TRAINS(final CharacterPower characterPower) { + + //skip update if nothing changed + + if (!characterPower.isTrained()) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_character_power` SET `trains`=? WHERE `UID`=?")) { + + preparedStatement.setShort(1, (short) characterPower.getTrains()); + preparedStatement.setInt(2, characterPower.getObjectUUID()); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + + characterPower.setTrained(false); + } + + public void updateDatabase(final CharacterPower pow) { + + if (pow.getPower() == null) { + Logger.error("Failed to find powersBase for Power " + pow.getObjectUUID()); + return; + } + + if (CharacterPower.getOwner(pow) == null) { + Logger.error("Failed to find owner for Power " + pow.getObjectUUID()); + return; + } + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_character_power` SET `PowersBaseToken`=?, `CharacterID`=?, `trains`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, pow.getPower().getToken()); + preparedStatement.setInt(2, CharacterPower.getOwner(pow).getObjectUUID()); + preparedStatement.setShort(3, (short) pow.getTrains()); + preparedStatement.setInt(4, pow.getObjectUUID()); + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + + pow.setTrained(false); + } +} diff --git a/src/engine/db/handlers/dbCharacterRuneHandler.java b/src/engine/db/handlers/dbCharacterRuneHandler.java new file mode 100644 index 00000000..7cdbed5c --- /dev/null +++ b/src/engine/db/handlers/dbCharacterRuneHandler.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.CharacterRune; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.util.ArrayList; + +public class dbCharacterRuneHandler extends dbHandlerBase { + + public dbCharacterRuneHandler() { + this.localClass = CharacterRune.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public CharacterRune ADD_CHARACTER_RUNE(final CharacterRune toAdd) { + + CharacterRune characterRune = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_rune` (`CharacterID`, `RuneBaseID`) VALUES (?, ?);", Statement.RETURN_GENERATED_KEYS)) { + + preparedStatement.setLong(1, toAdd.getPlayerID()); + preparedStatement.setInt(2, toAdd.getRuneBaseID()); + + preparedStatement.executeUpdate(); + ResultSet rs = preparedStatement.getGeneratedKeys(); + + if (rs.next()) + characterRune = GET_CHARACTER_RUNE(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + } + + return characterRune; + } + + public CharacterRune GET_CHARACTER_RUNE(int runeID) { + + CharacterRune characterRune = (CharacterRune) DbManager.getFromCache(Enum.GameObjectType.CharacterRune, runeID); + + if (characterRune != null) + return characterRune; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_character_rune` WHERE `UID`=?")) { + + preparedStatement.setInt(1, runeID); + ResultSet rs = preparedStatement.executeQuery(); + + characterRune = (CharacterRune) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return characterRune; + } + + public boolean DELETE_CHARACTER_RUNE(final CharacterRune characterRune) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_character_rune` WHERE `UID`=?;")) { + + preparedStatement.setLong(1, characterRune.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public ArrayList GET_RUNES_FOR_CHARACTER(final int characterId) { + + ArrayList characterRunes = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_character_rune` WHERE `CharacterID` = ?")) { + + preparedStatement.setInt(1, characterId); + + ResultSet rs = preparedStatement.executeQuery(); + characterRunes = getObjectsFromRs(rs, 10); + + } catch (SQLException e) { + Logger.error(e); + } + return characterRunes; + } + + public void updateDatabase(final CharacterRune characterRune) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_character_rune` SET `CharacterID`=?, `RuneBaseID`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, characterRune.getPlayerID()); + preparedStatement.setInt(2, characterRune.getRuneBaseID()); + preparedStatement.setLong(3, characterRune.getObjectUUID()); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } + +} diff --git a/src/engine/db/handlers/dbCharacterSkillHandler.java b/src/engine/db/handlers/dbCharacterSkillHandler.java new file mode 100644 index 00000000..49e88952 --- /dev/null +++ b/src/engine/db/handlers/dbCharacterSkillHandler.java @@ -0,0 +1,173 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.AbstractCharacter; +import engine.objects.CharacterSkill; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.util.concurrent.ConcurrentHashMap; + +public class dbCharacterSkillHandler extends dbHandlerBase { + + public dbCharacterSkillHandler() { + this.localClass = CharacterSkill.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public CharacterSkill ADD_SKILL(CharacterSkill toAdd) { + + CharacterSkill characterSkill = null; + + if (CharacterSkill.GetOwner(toAdd) == null || toAdd.getSkillsBase() == null) { + Logger.error("dbCharacterSkillHandler.ADD_SKILL", toAdd.getObjectUUID() + " missing owner or skillsBase"); + return null; + } + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_skill` (`CharacterID`, `skillsBaseID`, `trains`) VALUES (?, ?, ?);", Statement.RETURN_GENERATED_KEYS)) { + + preparedStatement.setLong(1, CharacterSkill.GetOwner(toAdd).getObjectUUID()); + preparedStatement.setInt(2, toAdd.getSkillsBase().getObjectUUID()); + preparedStatement.setInt(3, toAdd.getNumTrains()); + + preparedStatement.executeUpdate(); + ResultSet rs = preparedStatement.getGeneratedKeys(); + + if (rs.next()) + characterSkill = GET_SKILL(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + } + return characterSkill; + } + + public boolean DELETE_SKILL(final int objectUUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_character_skill` WHERE `UID` = ?")) { + + preparedStatement.setLong(1, objectUUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public CharacterSkill GET_SKILL(final int objectUUID) { + + CharacterSkill characterSkill = (CharacterSkill) DbManager.getFromCache(Enum.GameObjectType.CharacterSkill, objectUUID); + + if (characterSkill != null) + return characterSkill; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_character_skill` WHERE `UID` = ?")) { + + preparedStatement.setInt(1, objectUUID); + ResultSet rs = preparedStatement.executeQuery(); + + characterSkill = (CharacterSkill) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return characterSkill; + } + + public ConcurrentHashMap GET_SKILLS_FOR_CHARACTER(final AbstractCharacter ac) { + + ConcurrentHashMap characterSkills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + if (ac == null || (!(ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))) + return characterSkills; + + PlayerCharacter playerCharacter = (PlayerCharacter) ac; + int characterId = playerCharacter.getObjectUUID(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_character_skill` WHERE `CharacterID` = ?")) { + + preparedStatement.setInt(1, characterId); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + CharacterSkill cs = new CharacterSkill(rs, playerCharacter); + if (cs.getSkillsBase() != null) + characterSkills.put(cs.getSkillsBase().getName(), cs); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return characterSkills; + } + + + public void UPDATE_TRAINS(final CharacterSkill characterSkill) { + + if (!characterSkill.isTrained()) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_character_skill` SET `trains`=? WHERE `UID` = ?")) { + + preparedStatement.setShort(1, (short) characterSkill.getNumTrains()); + preparedStatement.setLong(2, characterSkill.getObjectUUID()); + + if (preparedStatement.executeUpdate() != 0) + characterSkill.syncTrains(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public void updateDatabase(final CharacterSkill characterSkill) { + + if (characterSkill.getSkillsBase() == null) { + Logger.error("Failed to find skillsBase for Skill " + characterSkill.getObjectUUID()); + return; + } + + if (CharacterSkill.GetOwner(characterSkill) == null) { + Logger.error("Failed to find owner for Skill " + characterSkill.getObjectUUID()); + return; + } + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_character_skill` SET `skillsBaseID`=?, `CharacterID`=?, `trains`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, characterSkill.getSkillsBase().getObjectUUID()); + preparedStatement.setInt(2, CharacterSkill.GetOwner(characterSkill).getObjectUUID()); + preparedStatement.setShort(3, (short) characterSkill.getNumTrains()); + preparedStatement.setLong(4, characterSkill.getObjectUUID()); + + if (preparedStatement.executeUpdate() != 0) + characterSkill.syncTrains(); + + } catch (SQLException e) { + Logger.error(e); + } + + } + +} diff --git a/src/engine/db/handlers/dbCityHandler.java b/src/engine/db/handlers/dbCityHandler.java new file mode 100644 index 00000000..bf3d9d98 --- /dev/null +++ b/src/engine/db/handlers/dbCityHandler.java @@ -0,0 +1,245 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.Zone; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.time.LocalDateTime; +import java.util.ArrayList; + +public class dbCityHandler extends dbHandlerBase { + + public dbCityHandler() { + this.localClass = City.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public static void addObject(ArrayList list, ResultSet rs) throws SQLException { + String type = rs.getString("type"); + switch (type) { + case "zone": + Zone zone = new Zone(rs); + DbManager.addToCache(zone); + list.add(zone); + break; + case "building": + Building building = new Building(rs); + DbManager.addToCache(building); + list.add(building); + break; + case "city": + City city = new City(rs); + DbManager.addToCache(city); + list.add(city); + break; + } + } + + public ArrayList CREATE_CITY(int ownerID, int parentZoneID, int realmID, float xCoord, float yCoord, float zCoord, float rotation, float W, String name, LocalDateTime established) { + + LocalDateTime upgradeTime = LocalDateTime.now().plusHours(2); + ArrayList objectList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `city_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?,?,?)")) { + + preparedStatement.setLong(1, ownerID); //objectUUID of owning player + preparedStatement.setLong(2, parentZoneID); //objectUUID of parent (continent) zone + preparedStatement.setLong(3, realmID); //objectUUID of realm city belongs in + preparedStatement.setFloat(4, xCoord); //xOffset from parentZone center + preparedStatement.setFloat(5, yCoord); //yOffset from parentZone center + preparedStatement.setFloat(6, zCoord); //zOffset from parentZone center + preparedStatement.setString(7, name); //city name + preparedStatement.setTimestamp(8, Timestamp.valueOf(established)); + preparedStatement.setFloat(9, rotation); + preparedStatement.setFloat(10, W); + preparedStatement.setTimestamp(11, Timestamp.valueOf(upgradeTime)); + + boolean work = preparedStatement.execute(); + + if (work) { + + ResultSet rs = preparedStatement.getResultSet(); + + while (rs.next()) + addObject(objectList, rs); + rs.close(); + } else { + Logger.info("City Placement Failed: " + preparedStatement); + return objectList; //city creation failure + } + while (preparedStatement.getMoreResults()) { + ResultSet rs = preparedStatement.getResultSet(); + while (rs.next()) { + addObject(objectList, rs); + } + rs.close(); + } + } catch (SQLException e) { + Logger.error(e); + } + + return objectList; + } + + public ArrayList GET_CITIES_BY_ZONE(final int objectUUID) { + + ArrayList cityList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_city`.*, `object`.`parent` FROM `obj_city` INNER JOIN `object` ON `object`.`UID` = `obj_city`.`UID` WHERE `object`.`parent`=?;")) { + + preparedStatement.setLong(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + cityList = getObjectsFromRs(rs, 100); + + } catch (SQLException e) { + Logger.error(e); + } + + return cityList; + } + + public City GET_CITY(final int cityId) { + + City city = (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId); + + if (city != null) + return city; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_city`.*, `object`.`parent` FROM `obj_city` INNER JOIN `object` ON `object`.`UID` = `obj_city`.`UID` WHERE `object`.`UID`=?;")) { + + preparedStatement.setLong(1, cityId); + + ResultSet rs = preparedStatement.executeQuery(); + city = (City) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return city; + } + + public boolean updateforceRename(City city, boolean value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `forceRename`=?" + + " WHERE `UID` = ?")) { + + preparedStatement.setByte(1, (value == true) ? (byte) 1 : (byte) 0); + preparedStatement.setInt(2, city.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean updateOpenCity(City city, boolean value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `open`=?" + + " WHERE `UID` = ?")) { + + preparedStatement.setByte(1, (value == true) ? (byte) 1 : (byte) 0); + preparedStatement.setInt(2, city.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean updateTOL(City city, int tolID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `treeOfLifeUUID`=?" + + " WHERE `UID` = ?")) { + + preparedStatement.setInt(1, tolID); + preparedStatement.setInt(2, city.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + public boolean renameCity(City city, String name) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `name`=?" + + " WHERE `UID` = ?")) { + + preparedStatement.setString(1, name); + preparedStatement.setInt(2, city.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + public boolean updateSiegesWithstood(City city, int value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `name`=?" + + " WHERE `UID` = ?")) { + + preparedStatement.setInt(1, value); + preparedStatement.setInt(2, city.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + public boolean updateRealmTaxDate(City city, LocalDateTime localDateTime) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `realmTaxDate` =?" + + " WHERE `UID` = ?")) { + + preparedStatement.setTimestamp(1, Timestamp.valueOf(localDateTime)); + preparedStatement.setInt(2, city.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + +} diff --git a/src/engine/db/handlers/dbContractHandler.java b/src/engine/db/handlers/dbContractHandler.java new file mode 100644 index 00000000..323b349d --- /dev/null +++ b/src/engine/db/handlers/dbContractHandler.java @@ -0,0 +1,168 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.Contract; +import engine.objects.ItemBase; +import engine.objects.MobEquipment; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class dbContractHandler extends dbHandlerBase { + + public dbContractHandler() { + this.localClass = Contract.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public Contract GET_CONTRACT(final int objectUUID) { + + Contract contract = (Contract) DbManager.getFromCache(Enum.GameObjectType.Contract, objectUUID); + + if (contract != null) + return contract; + + if (objectUUID == 0) + return null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_contract` WHERE `ID` = ?")) { + + preparedStatement.setInt(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + contract = (Contract) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return contract; + } + + public void LOAD_CONTRACT_INVENTORY(final Contract contract) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_inventoryset` WHERE `inventorySet` = ?;")) { + + preparedStatement.setInt(1, contract.inventorySet); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + //handle item base + int itemBaseID = rs.getInt("itembaseID"); + + ItemBase ib = ItemBase.getItemBase(itemBaseID); + + if (ib != null) { + + MobEquipment me = new MobEquipment(ib, 0, 0); + contract.getSellInventory().add(me); + + //handle magic effects + String prefix = rs.getString("prefix"); + int pRank = rs.getInt("pRank"); + String suffix = rs.getString("suffix"); + int sRank = rs.getInt("sRank"); + + if (prefix != null) { + me.setPrefix(prefix, pRank); + me.setIsID(true); + } + + if (suffix != null) { + me.setSuffix(suffix, sRank); + me.setIsID(true); + } + + } + } + } catch (SQLException e) { + Logger.error(e); + } + } + + public void LOAD_SELL_LIST_FOR_CONTRACT(final Contract contract) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_contract_selltype` WHERE `contractID` = ?;")) { + + preparedStatement.setInt(1, contract.getObjectUUID()); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + int type = rs.getInt("type"); + int value = rs.getInt("value"); + + switch (type) { + case 1: + contract.getBuyItemType().add(value); + break; + case 2: + contract.getBuySkillToken().add(value); + break; + case 3: + contract.getBuyUnknownToken().add(value); + break; + } + } + } catch (SQLException e) { + Logger.error(e); + } + } + + public boolean updateAllowedBuildings(final Contract con, final long slotbitvalue) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `static_npc_contract` SET `allowedBuildingTypeID`=? WHERE `contractID`=?")) { + + preparedStatement.setLong(1, slotbitvalue); + preparedStatement.setInt(2, con.getContractID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateDatabase(final Contract con) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `static_npc_contract` SET `contractID`=?, `name`=?, " + + "`mobbaseID`=?, `classID`=?, vendorDialog=?, iconID=?, allowedBuildingTypeID=? WHERE `ID`=?")) { + + preparedStatement.setInt(1, con.getContractID()); + preparedStatement.setString(2, con.getName()); + preparedStatement.setInt(3, con.getMobbaseID()); + preparedStatement.setInt(4, con.getClassID()); + preparedStatement.setInt(5, (con.getVendorDialog() != null) ? con.getVendorDialog().getObjectUUID() : 0); + preparedStatement.setInt(6, con.getIconID()); + preparedStatement.setInt(8, con.getObjectUUID()); + preparedStatement.setLong(7, con.getAllowedBuildings().toLong()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } +} diff --git a/src/engine/db/handlers/dbEffectsBaseHandler.java b/src/engine/db/handlers/dbEffectsBaseHandler.java new file mode 100644 index 00000000..4836ec6e --- /dev/null +++ b/src/engine/db/handlers/dbEffectsBaseHandler.java @@ -0,0 +1,303 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.powers.EffectsBase; +import engine.powers.effectmodifiers.*; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; + +public class dbEffectsBaseHandler extends dbHandlerBase { + + public dbEffectsBaseHandler() { + + } + + public static ArrayList getAllEffectsBase() { + + ArrayList effectList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement prepareStatement = connection.prepareStatement("SELECT * FROM static_power_effectbase ORDER BY `IDString` DESC")) { + + ResultSet rs = prepareStatement.executeQuery(); + + while (rs.next()) { + EffectsBase effectBase = new EffectsBase(rs); + effectList.add(effectBase); + } + } catch (SQLException e) { + Logger.error(e.toString()); + } + + return effectList; + } + + public static void cacheAllEffectModifiers() { + + String IDString; + AbstractEffectModifier abstractEffectModifier = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement prepareStatement = connection.prepareStatement("SELECT * FROM static_power_effectmod")) { + + ResultSet rs = prepareStatement.executeQuery(); + + while (rs.next()) { + + IDString = rs.getString("IDString"); + EffectsBase effectBase = PowersManager.getEffectByIDString(IDString); + Enum.ModType modifier = Enum.ModType.GetModType(rs.getString("modType")); + + //combine item prefix and suffix effect modifiers + + abstractEffectModifier = getCombinedModifiers(abstractEffectModifier, rs, effectBase, modifier); + + if (abstractEffectModifier != null) { + + if (EffectsBase.modifiersMap.containsKey(effectBase.getIDString()) == false) + EffectsBase.modifiersMap.put(effectBase.getIDString(), new HashSet<>()); + + EffectsBase.modifiersMap.get(effectBase.getIDString()).add(abstractEffectModifier); + + } + + } + + } catch (Exception e) { + Logger.error(e); + } + + } + + private static AbstractEffectModifier getCombinedModifiers(AbstractEffectModifier abstractEffectModifier, ResultSet rs, EffectsBase effectBase, Enum.ModType modifier) throws SQLException { + switch (modifier) { + case AdjustAboveDmgCap: + abstractEffectModifier = new AdjustAboveDmgCapEffectModifier(rs); + break; + case Ambidexterity: + abstractEffectModifier = new AmbidexterityEffectModifier(rs); + break; + case AnimOverride: + break; + case ArmorPiercing: + abstractEffectModifier = new ArmorPiercingEffectModifier(rs); + break; + case AttackDelay: + abstractEffectModifier = new AttackDelayEffectModifier(rs); + break; + case Attr: + abstractEffectModifier = new AttributeEffectModifier(rs); + break; + case BlackMantle: + abstractEffectModifier = new BlackMantleEffectModifier(rs); + break; + case BladeTrails: + abstractEffectModifier = new BladeTrailsEffectModifier(rs); + break; + case Block: + abstractEffectModifier = new BlockEffectModifier(rs); + break; + case BlockedPowerType: + abstractEffectModifier = new BlockedPowerTypeEffectModifier(rs); + break; + case CannotAttack: + abstractEffectModifier = new CannotAttackEffectModifier(rs); + break; + case CannotCast: + abstractEffectModifier = new CannotCastEffectModifier(rs); + break; + case CannotMove: + abstractEffectModifier = new CannotMoveEffectModifier(rs); + break; + case CannotTrack: + abstractEffectModifier = new CannotTrackEffectModifier(rs); + break; + case Charmed: + abstractEffectModifier = new CharmedEffectModifier(rs); + break; + case ConstrainedAmbidexterity: + abstractEffectModifier = new ConstrainedAmbidexterityEffectModifier(rs); + break; + case DamageCap: + abstractEffectModifier = new DamageCapEffectModifier(rs); + break; + case DamageShield: + abstractEffectModifier = new DamageShieldEffectModifier(rs); + break; + case DCV: + abstractEffectModifier = new DCVEffectModifier(rs); + break; + case Dodge: + abstractEffectModifier = new DodgeEffectModifier(rs); + break; + case DR: + abstractEffectModifier = new DREffectModifier(rs); + break; + case Durability: + abstractEffectModifier = new DurabilityEffectModifier(rs); + break; + case ExclusiveDamageCap: + abstractEffectModifier = new ExclusiveDamageCapEffectModifier(rs); + break; + case Fade: + abstractEffectModifier = new FadeEffectModifier(rs); + break; + case Fly: + abstractEffectModifier = new FlyEffectModifier(rs); + break; + case Health: + abstractEffectModifier = new HealthEffectModifier(rs); + break; + case HealthFull: + abstractEffectModifier = new HealthFullEffectModifier(rs); + break; + case HealthRecoverRate: + abstractEffectModifier = new HealthRecoverRateEffectModifier(rs); + break; + case IgnoreDamageCap: + abstractEffectModifier = new IgnoreDamageCapEffectModifier(rs); + break; + case IgnorePassiveDefense: + abstractEffectModifier = new IgnorePassiveDefenseEffectModifier(rs); + break; + case ImmuneTo: + abstractEffectModifier = new ImmuneToEffectModifier(rs); + break; + case ImmuneToAttack: + abstractEffectModifier = new ImmuneToAttackEffectModifier(rs); + break; + case ImmuneToPowers: + abstractEffectModifier = new ImmuneToPowersEffectModifier(rs); + break; + case Invisible: + abstractEffectModifier = new InvisibleEffectModifier(rs); + break; + case ItemName: + abstractEffectModifier = new ItemNameEffectModifier(rs); + if (((ItemNameEffectModifier) abstractEffectModifier).name.isEmpty()) + break; + if (effectBase != null) + effectBase.setName((((ItemNameEffectModifier) abstractEffectModifier).name)); + break; + case Mana: + abstractEffectModifier = new ManaEffectModifier(rs); + break; + case ManaFull: + abstractEffectModifier = new ManaFullEffectModifier(rs); + break; + case ManaRecoverRate: + abstractEffectModifier = new ManaRecoverRateEffectModifier(rs); + break; + case MaxDamage: + abstractEffectModifier = new MaxDamageEffectModifier(rs); + break; + case MeleeDamageModifier: + abstractEffectModifier = new MeleeDamageEffectModifier(rs); + break; + case MinDamage: + abstractEffectModifier = new MinDamageEffectModifier(rs); + break; + case NoMod: + abstractEffectModifier = new NoModEffectModifier(rs); + break; + case OCV: + abstractEffectModifier = new OCVEffectModifier(rs); + break; + case Parry: + abstractEffectModifier = new ParryEffectModifier(rs); + break; + case PassiveDefense: + abstractEffectModifier = new PassiveDefenseEffectModifier(rs); + break; + case PowerCost: + abstractEffectModifier = new PowerCostEffectModifier(rs); + break; + case PowerCostHealth: + abstractEffectModifier = new PowerCostHealthEffectModifier(rs); + break; + case PowerDamageModifier: + abstractEffectModifier = new PowerDamageEffectModifier(rs); + break; + case ProtectionFrom: + abstractEffectModifier = new ProtectionFromEffectModifier(rs); + break; + case Resistance: + abstractEffectModifier = new ResistanceEffectModifier(rs); + break; + case ScaleHeight: + abstractEffectModifier = new ScaleHeightEffectModifier(rs); + break; + case ScaleWidth: + abstractEffectModifier = new ScaleWidthEffectModifier(rs); + break; + case ScanRange: + abstractEffectModifier = new ScanRangeEffectModifier(rs); + break; + case SeeInvisible: + abstractEffectModifier = new SeeInvisibleEffectModifier(rs); + break; + case Silenced: + abstractEffectModifier = new SilencedEffectModifier(rs); + break; + case Skill: + abstractEffectModifier = new SkillEffectModifier(rs); + break; + case Slay: + abstractEffectModifier = new SlayEffectModifier(rs); + break; + case Speed: + abstractEffectModifier = new SpeedEffectModifier(rs); + break; + case SpireBlock: + abstractEffectModifier = new SpireBlockEffectModifier(rs); + break; + case Stamina: + abstractEffectModifier = new StaminaEffectModifier(rs); + break; + case StaminaFull: + abstractEffectModifier = new StaminaFullEffectModifier(rs); + break; + case StaminaRecoverRate: + abstractEffectModifier = new StaminaRecoverRateEffectModifier(rs); + break; + case Stunned: + abstractEffectModifier = new StunnedEffectModifier(rs); + break; + case Value: + abstractEffectModifier = new ValueEffectModifier(rs); + if (effectBase != null) { + ValueEffectModifier valueEffect = (ValueEffectModifier) abstractEffectModifier; + effectBase.setValue(valueEffect.minMod); + } + break; + case WeaponProc: + abstractEffectModifier = new WeaponProcEffectModifier(rs); + break; + case WeaponRange: + abstractEffectModifier = new WeaponRangeEffectModifier(rs); + break; + case WeaponSpeed: + abstractEffectModifier = new WeaponSpeedEffectModifier(rs); + break; + + } + return abstractEffectModifier; + } +} diff --git a/src/engine/db/handlers/dbEffectsResourceCostHandler.java b/src/engine/db/handlers/dbEffectsResourceCostHandler.java new file mode 100644 index 00000000..deae93a9 --- /dev/null +++ b/src/engine/db/handlers/dbEffectsResourceCostHandler.java @@ -0,0 +1,47 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.EffectsResourceCosts; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbEffectsResourceCostHandler extends dbHandlerBase { + + public dbEffectsResourceCostHandler() { + this.localClass = EffectsResourceCosts.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList GET_ALL_EFFECT_RESOURCES(String idString) { + + ArrayList effectsResourceCosts = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_power_effectcost` WHERE `IDString` = ?")) { + + preparedStatement.setString(1, idString); + + ResultSet rs = preparedStatement.executeQuery(); + effectsResourceCosts = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + + return effectsResourceCosts; + } +} diff --git a/src/engine/db/handlers/dbEnchantmentHandler.java b/src/engine/db/handlers/dbEnchantmentHandler.java new file mode 100644 index 00000000..87e211de --- /dev/null +++ b/src/engine/db/handlers/dbEnchantmentHandler.java @@ -0,0 +1,75 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; + +public class dbEnchantmentHandler extends dbHandlerBase { + + public ConcurrentHashMap GET_ENCHANTMENTS_FOR_ITEM(final int id) { + + ConcurrentHashMap enchants = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_item_enchantment` WHERE `ItemID`=?;")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + enchants.put(rs.getString("powerAction"), rs.getInt("rank")); + + } catch (SQLException e) { + Logger.error(e); + } + + return enchants; + } + + public boolean CREATE_ENCHANTMENT_FOR_ITEM(long itemID, String powerAction, int rank) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_item_enchantment` (`itemID`, `powerAction`, `rank`) VALUES (?, ?, ?);")) { + + preparedStatement.setLong(1, itemID); + preparedStatement.setString(2, powerAction); + preparedStatement.setInt(3, rank); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean CLEAR_ENCHANTMENTS(long itemID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_item_enchantment` WHERE `itemID`=?;")) { + + preparedStatement.setLong(1, itemID); + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } +} diff --git a/src/engine/db/handlers/dbGuildHandler.java b/src/engine/db/handlers/dbGuildHandler.java new file mode 100644 index 00000000..a4f1998e --- /dev/null +++ b/src/engine/db/handlers/dbGuildHandler.java @@ -0,0 +1,718 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.Enum.GuildHistoryType; +import engine.gameManager.DbManager; +import engine.objects.*; +import engine.server.world.WorldServer; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.util.ArrayList; + +public class dbGuildHandler extends dbHandlerBase { + + public dbGuildHandler() { + this.localClass = Guild.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public static ArrayList GET_GUILD_BANISHED(final int id) { + + return new ArrayList<>(); + + // Bugfix + // prepareCallable("SELECT * FROM `obj_character`, `dyn_guild_banishlist` WHERE `obj_character.char_isActive` = 1 AND `dyn_guild_banishlist.CharacterID` = `obj_character.UID` AND `obj_character.GuildID`=?"); + + //prepareCallable("SELECT * FROM `obj_character` `,` `dyn_guild_banishlist` WHERE obj_character.char_isActive = 1 AND dyn_guild_banishlist.CharacterID = obj_character.UID AND dyn_guild_banishlist.GuildID = ?"); + //setLong(1, (long) id); + + //return getObjectList(); + } + + public static void LOAD_GUILD_HISTORY_FOR_PLAYER(PlayerCharacter playerCharacter) { + + if (playerCharacter == null) + return; + + ArrayList guildList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_guild_allianceenemylist` WHERE `GuildID` = ?")) { + + preparedStatement.setInt(1, playerCharacter.getObjectUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + GuildHistory guildEntry = new GuildHistory(rs); + guildList.add(guildEntry); + } + + } catch (SQLException e) { + Logger.error(e); + } + + playerCharacter.setGuildHistory(guildList); + } + + public int BANISH_FROM_GUILD_OFFLINE(final int target, boolean sourceIsGuildLeader) { + + String queryString; + int rowCount; + + // Only a Guild Leader can remove inner council + + if (sourceIsGuildLeader) + queryString = "UPDATE `obj_character` SET `guildUID`=NULL, `guild_isInnerCouncil`=0, `guild_isTaxCollector`=0," + + " `guild_isRecruiter`=0, `guild_isFullMember`=0, `guild_title`=0 WHERE `UID`=?"; + else + queryString = "UPDATE `obj_character` SET `guildUID`=NULL, `guild_isInnerCouncil`=0, `guild_isTaxCollector`=0," + + " `guild_isRecruiter`=0, `guild_isFullMember`=0, `guild_title`=0 WHERE `UID`=? && `guild_isInnerCouncil`=0"; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(queryString)) { + + preparedStatement.setLong(1, target); + rowCount = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + return 0; + } + + return rowCount; + } + + public boolean ADD_TO_BANISHED_FROM_GUILDLIST(int target, long characterID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_guild_banishlist` (`GuildID`, `CharacterID`) VALUES (?,?)")) { + + preparedStatement.setLong(1, target); + preparedStatement.setLong(2, characterID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean REMOVE_FROM_BANISH_LIST(int target, long characterID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_guild_banishlist` (`GuildID`, `CharacterID`) VALUES (?,?)")) { + + preparedStatement.setLong(1, target); + preparedStatement.setLong(2, characterID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean ADD_TO_GUILDHISTORY(int target, PlayerCharacter playerCharacter, DateTime historyDate, GuildHistoryType historyType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_guildhistory` (`GuildID`, `CharacterID`, `historyDate`, `historyType`) VALUES (?,?,?,?)")) { + + preparedStatement.setLong(1, target); + preparedStatement.setLong(2, playerCharacter.getObjectUUID()); + + if (historyDate == null) + preparedStatement.setNull(3, java.sql.Types.DATE); + else + preparedStatement.setTimestamp(3, new Timestamp(historyDate.getMillis())); + + preparedStatement.setString(4, historyType.name()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + //TODO Need to get this working. + public ArrayList GET_GUILD_HISTORY_OF_PLAYER(final int id) { + + ArrayList guildList = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT g.* FROM `obj_guild` g, `dyn_character_guildhistory` l WHERE g.`UID` = l.`GuildID` && l.`CharacterID` = ?")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + guildList = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return guildList; + } + + public ArrayList GET_GUILD_ALLIES(final int id) { + + ArrayList guildList = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT g.* FROM `obj_guild` g, `dyn_guild_allianceenemylist` l " + + "WHERE l.isAlliance = 1 && l.OtherGuildID = g.UID && l.GuildID=?")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + guildList = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return guildList; + + } + + public ArrayList GET_GUILD_ENEMIES(final int id) { + + ArrayList guildList = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT g.* FROM `obj_guild` g, `dyn_guild_allianceenemylist` l " + + "WHERE l.isAlliance = 0 && l.OtherGuildID = g.UID && l.GuildID=?")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + guildList = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return guildList; + + } + + public ArrayList GET_GUILD_KOS_CHARACTER(final int id) { + + ArrayList kosList = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT c.* FROM `obj_character` c, `dyn_guild_characterkoslist` l WHERE c.`char_isActive` = 1 && l.`KOSCharacterID` = c.`UID` && l.`GuildID`=?")) { + + preparedStatement.setLong(1, id); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + int playerUUID = rs.getInt(1); + PlayerCharacter kosPlayer = PlayerCharacter.getPlayerCharacter(playerUUID); + + if (kosPlayer != null) + kosList.add(kosPlayer); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return kosList; + } + + public ArrayList GET_GUILD_KOS_GUILD(final int id) { + + ArrayList guildList = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT g.* FROM `obj_guild` g, `dyn_guild_guildkoslist` l " + + "WHERE l.KOSGuildID = g.UID && l.GuildID = ?")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + guildList = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return guildList; + } + + public ArrayList GET_SUB_GUILDS(final int guildID) { + + ArrayList guildList = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_guild`.*, `object`.`parent` FROM `object` INNER JOIN `obj_guild` ON `obj_guild`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;")) { + + preparedStatement.setInt(1, guildID); + + ResultSet rs = preparedStatement.executeQuery(); + guildList = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return guildList; + } + + public Guild GET_GUILD(int id) { + + Guild guild = (Guild) DbManager.getFromCache(Enum.GameObjectType.Guild, id); + + if (guild != null) + return guild; + + if (id == 0) + return Guild.getErrantGuild(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_guild`.*, `object`.`parent` FROM `obj_guild` INNER JOIN `object` ON `object`.`UID` = `obj_guild`.`UID` WHERE `object`.`UID`=?")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + guild = (Guild) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return guild; + + } + + public ArrayList GET_ALL_GUILDS() { + + ArrayList guildList = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_guild`.*, `object`.`parent` FROM `obj_guild` INNER JOIN `object` ON `object`.`UID` = `obj_guild`.`UID`")) { + + ResultSet rs = preparedStatement.executeQuery(); + guildList = getObjectsFromRs(rs, 100); + + } catch (SQLException e) { + Logger.error(e); + } + return guildList; + } + + public boolean IS_CREST_UNIQUE(final GuildTag gt) { + + boolean valid = false; + String queryString; + + // Ignore bg symbol if bg color is the same as fg color. + + if (gt.backgroundColor01 == gt.backgroundColor02) + queryString = "SELECT `name` FROM `obj_guild` WHERE `backgroundColor01`=? && `backgroundColor02`=? && `symbolColor`=? && `symbol`=?;"; + else + queryString = "SELECT `name` FROM `obj_guild` WHERE `backgroundColor01`=? && `backgroundColor02`=? && `symbolColor`=? && `backgroundDesign`=? && `symbol`=?;"; + + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(queryString)) { + + if (gt.backgroundColor01 == gt.backgroundColor02) { + preparedStatement.setInt(1, gt.backgroundColor01); + preparedStatement.setInt(2, gt.backgroundColor02); + preparedStatement.setInt(3, gt.symbolColor); + preparedStatement.setInt(4, gt.symbol); + } else { + preparedStatement.setInt(1, gt.backgroundColor01); + preparedStatement.setInt(2, gt.backgroundColor02); + preparedStatement.setInt(3, gt.symbolColor); + preparedStatement.setInt(4, gt.backgroundDesign); + preparedStatement.setInt(5, gt.symbol); + } + + ResultSet rs = preparedStatement.executeQuery(); + + if (!rs.next()) + valid = true; + + } catch (SQLException e) { + Logger.error(e); + } + + return valid; + } + + public boolean SET_GUILD_OWNED_CITY(int guildID, int cityID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `ownedCity`=? WHERE `UID`=?")) { + + preparedStatement.setLong(1, cityID); + preparedStatement.setLong(2, guildID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_GUILD_LEADER(int objectUUID, int guildID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `leaderUID`=? WHERE `UID`=?")) { + + preparedStatement.setLong(1, objectUUID); + preparedStatement.setLong(2, guildID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean IS_NAME_UNIQUE(final String name) { + + boolean valid = false; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `name` FROM `obj_guild` WHERE `name`=?;")) { + + preparedStatement.setString(1, name); + ResultSet rs = preparedStatement.executeQuery(); + + if (!rs.next()) + valid = true; + + } catch (SQLException e) { + Logger.error(e); + } + + return valid; + } + + public Guild SAVE_TO_DATABASE(Guild g) { + + Guild guild = null; + GuildTag guildTag = g.getGuildTag(); + + if (guildTag == null) + return null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `guild_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) { + + preparedStatement.setLong(1, WorldServer.worldUUID); + preparedStatement.setLong(2, g.getGuildLeaderUUID()); + preparedStatement.setString(3, g.getName()); + preparedStatement.setInt(4, guildTag.backgroundColor01); + preparedStatement.setInt(5, guildTag.backgroundColor02); + preparedStatement.setInt(6, guildTag.symbolColor); + preparedStatement.setInt(7, guildTag.backgroundDesign); + preparedStatement.setInt(8, guildTag.symbol); + preparedStatement.setInt(9, g.getCharter()); + preparedStatement.setString(10, g.getLeadershipType()); + preparedStatement.setString(11, g.getMotto()); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + int objectUUID = (int) rs.getLong("UID"); + + if (objectUUID > 0) + guild = GET_GUILD(objectUUID); + } + + } catch (SQLException e) { + Logger.error(e); + } + return guild; + } + + public boolean UPDATE_GUILD_RANK_OFFLINE(int target, int newRank, int guildId) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guild_title`=? WHERE `UID`=? && `guildUID`=?")) { + + preparedStatement.setInt(1, newRank); + preparedStatement.setInt(2, target); + preparedStatement.setInt(3, guildId); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_PARENT(int guildUID, int parentUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` SET `parent`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, parentUID); + preparedStatement.setInt(2, guildUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public int DELETE_GUILD(final Guild guild) { + + int row_count = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) { + + preparedStatement.setLong(1, guild.getObjectUUID()); + row_count = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + } + return row_count; + } + + public boolean UPDATE_MINETIME(int guildUID, int mineTime) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `mineTime`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, mineTime); + preparedStatement.setInt(2, guildUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + + // *** Refactor: Why are we saving tags/charter in update? + // It's not like this shit ever changes. + + public int UPDATE_GUILD_STATUS_OFFLINE(int target, boolean isInnerCouncil, boolean isRecruiter, boolean isTaxCollector, int guildId) { + + int updateMask = 0; + int row_count = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `guild_isInnerCouncil`, `guild_isTaxCollector`, `guild_isRecruiter` FROM `obj_character` WHERE `UID`=? && `guildUID`=?")) { + + preparedStatement.setLong(1, target); + preparedStatement.setLong(2, guildId); + + ResultSet rs = preparedStatement.executeQuery(); + + //If the first query had no results, neither will the second + + //Determine what is different + + if (rs.first()) { + if (rs.getBoolean("guild_isInnerCouncil") != isInnerCouncil) + updateMask |= 4; + if (rs.getBoolean("guild_isRecruiter") != isRecruiter) + updateMask |= 2; + if (rs.getBoolean("guild_isTaxCollector") != isTaxCollector) + updateMask |= 1; + } + } catch (SQLException e) { + Logger.error(e); + } + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guild_isInnerCouncil`=?, `guild_isTaxCollector`=?, `guild_isRecruiter`=?, `guild_isFullMember`=? WHERE `UID`=? && `guildUID`=?")) { + + preparedStatement.setBoolean(1, isInnerCouncil); + preparedStatement.setBoolean(2, isRecruiter); + preparedStatement.setBoolean(3, isTaxCollector); + preparedStatement.setBoolean(4, ((updateMask > 0))); //If you are becoming an officer, or where an officer, your a full member... + preparedStatement.setLong(5, target); + preparedStatement.setLong(6, guildId); + + row_count = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + } + return row_count; + } + + public boolean updateDatabase(final Guild g) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `name`=?, `backgroundColor01`=?, `backgroundColor02`=?, `symbolColor`=?, `backgroundDesign`=?, `symbol`=?, `charter`=?, `motd`=?, `icMotd`=?, `nationMotd`=?, `leaderUID`=? WHERE `UID`=?")) { + + preparedStatement.setString(1, g.getName()); + preparedStatement.setInt(2, g.getGuildTag().backgroundColor01); + preparedStatement.setInt(3, g.getGuildTag().backgroundColor02); + preparedStatement.setInt(4, g.getGuildTag().symbolColor); + preparedStatement.setInt(5, g.getGuildTag().backgroundDesign); + preparedStatement.setInt(6, g.getGuildTag().symbol); + preparedStatement.setInt(7, g.getCharter()); + preparedStatement.setString(8, g.getMOTD()); + preparedStatement.setString(9, g.getICMOTD()); + preparedStatement.setString(10, ""); + preparedStatement.setInt(11, g.getGuildLeaderUUID()); + preparedStatement.setLong(12, g.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean ADD_TO_ALLIANCE_LIST(final long sourceGuildID, final long targetGuildID, boolean isRecommended, boolean isAlly, String recommender) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_guild_allianceenemylist` (`GuildID`, `OtherGuildID`,`isRecommended`, `isAlliance`, `recommender`) VALUES (?,?,?,?,?)")) { + + preparedStatement.setLong(1, sourceGuildID); + preparedStatement.setLong(2, targetGuildID); + preparedStatement.setBoolean(3, isRecommended); + preparedStatement.setBoolean(4, isAlly); + preparedStatement.setString(5, recommender); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean REMOVE_FROM_ALLIANCE_LIST(final long sourceGuildID, long targetGuildID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_guild_allianceenemylist` WHERE `GuildID`=? AND `OtherGuildID`=?")) { + + preparedStatement.setLong(1, sourceGuildID); + preparedStatement.setLong(2, targetGuildID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_RECOMMENDED(final long sourceGuildID, long targetGuildID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_guild_allianceenemylist` SET `isRecommended` = ? WHERE `GuildID`=? AND `OtherGuildID`=?")) { + + preparedStatement.setLong(1, sourceGuildID); + preparedStatement.setLong(2, targetGuildID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_ALLIANCE(final long sourceGuildID, long targetGuildID, boolean isAlly) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_guild_allianceenemylist` SET `isAlliance` = ? WHERE `GuildID`=? AND `OtherGuildID`=?")) { + + preparedStatement.setLong(1, sourceGuildID); + preparedStatement.setLong(2, targetGuildID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_ALLIANCE_AND_RECOMMENDED(final long sourceGuildID, long targetGuildID, boolean isAlly) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_guild_allianceenemylist` SET `isRecommended` = ?, `isAlliance` = ? WHERE `GuildID`=? AND `OtherGuildID`=?")) { + + preparedStatement.setByte(1, (byte) 0); + preparedStatement.setBoolean(2, isAlly); + preparedStatement.setLong(3, sourceGuildID); + preparedStatement.setLong(4, targetGuildID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + public void LOAD_ALL_ALLIANCES_FOR_GUILD(Guild guild) { + + if (guild == null) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_guild_allianceenemylist` WHERE `GuildID` = ?")) { + + preparedStatement.setInt(1, guild.getObjectUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + GuildAlliances guildAlliance = new GuildAlliances(rs); + guild.guildAlliances.put(guildAlliance.getAllianceGuild(), guildAlliance); + } + + } catch (SQLException e) { + Logger.error(e); + } + } + + //TODO uncomment this when finished with guild history warehouse integration +// public HashMap GET_WAREHOUSE_GUILD_HISTORY(){ +// +// HashMap tempMap = new HashMap<>(); +// prepareCallable("SELECT * FROM `warehouse_guildhistory` WHERE `eventType` = 'CREATE'"); +// try { +// ResultSet rs = executeQuery(); +// +// while (rs.next()) { +// GuildRecord guildRecord = new GuildRecord(rs); +// tempMap.put(guildRecord.guildID, guildRecord); +// } +// }catch (Exception e){ +// Logger.error(e); +// } +// return tempMap; +// +// } + + +} diff --git a/src/engine/db/handlers/dbHandlerBase.java b/src/engine/db/handlers/dbHandlerBase.java new file mode 100644 index 00000000..1e1ee851 --- /dev/null +++ b/src/engine/db/handlers/dbHandlerBase.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.AbstractWorldObject; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.util.ArrayList; + +public abstract class dbHandlerBase { + + protected Class localClass = null; + protected GameObjectType localObjectType; + + protected AbstractGameObject getObjectFromRs(ResultSet rs) { + + AbstractGameObject abstractGameObject = null; + + try { + if (rs.next()) { + abstractGameObject = localClass.getConstructor(ResultSet.class).newInstance(rs); + + DbManager.addToCache(abstractGameObject); + } + } catch (Exception e) { + Logger.error(e); + } + + // Only call runAfterLoad() for objects instanced on the world server + + if ((abstractGameObject != null && abstractGameObject instanceof AbstractWorldObject) && + (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER) || + (abstractGameObject.getObjectType() == GameObjectType.Guild))) + ((AbstractWorldObject) abstractGameObject).runAfterLoad(); + + return abstractGameObject; + } + + protected ArrayList getObjectsFromRs(ResultSet rs, int listSize) { + + ArrayList objectList = new ArrayList<>(listSize); + + try { + while (rs.next()) { + + int id = rs.getInt(1); + + if (DbManager.inCache(localObjectType, id)) { + objectList.add((T) DbManager.getFromCache(localObjectType, id)); + } else { + AbstractGameObject toAdd = localClass.getConstructor(ResultSet.class).newInstance(rs); + DbManager.addToCache(toAdd); + objectList.add((T) toAdd); + + if (toAdd != null && toAdd instanceof AbstractWorldObject) + ((AbstractWorldObject) toAdd).runAfterLoad(); + } + } + } catch (Exception e) { + Logger.error(e); + } + return objectList; + } +} diff --git a/src/engine/db/handlers/dbHeightMapHandler.java b/src/engine/db/handlers/dbHeightMapHandler.java new file mode 100644 index 00000000..eec0bfbc --- /dev/null +++ b/src/engine/db/handlers/dbHeightMapHandler.java @@ -0,0 +1,43 @@ +package engine.db.handlers; + +import engine.InterestManagement.HeightMap; +import engine.gameManager.DbManager; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class dbHeightMapHandler extends dbHandlerBase { + + public dbHeightMapHandler() { + + + } + + public void LOAD_ALL_HEIGHTMAPS() { + + HeightMap thisHeightmap; + HeightMap.heightMapsCreated = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_zone_heightmap INNER JOIN static_zone_size ON static_zone_size.loadNum = static_zone_heightmap.zoneLoadID")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + thisHeightmap = new HeightMap(rs); + + if (thisHeightmap.getHeightmapImage() == null) { + Logger.info("Imagemap for " + thisHeightmap.getHeightMapID() + " was null"); + continue; + } + } + + } catch (SQLException e) { + Logger.error(e); + } + } + +} diff --git a/src/engine/db/handlers/dbItemBaseHandler.java b/src/engine/db/handlers/dbItemBaseHandler.java new file mode 100644 index 00000000..bf12b434 --- /dev/null +++ b/src/engine/db/handlers/dbItemBaseHandler.java @@ -0,0 +1,138 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.ItemBase; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class dbItemBaseHandler extends dbHandlerBase { + + public dbItemBaseHandler() { + + } + + public void LOAD_BAKEDINSTATS(ItemBase itemBase) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_item_bakedinstat` WHERE `itemID` = ?")) { + + preparedStatement.setInt(1, itemBase.getUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + if (rs.getBoolean("fromUse")) + itemBase.getUsedStats().put(rs.getInt("token"), rs.getInt("numTrains")); + else + itemBase.getBakedInStats().put(rs.getInt("token"), rs.getInt("numTrains")); + } + } catch (SQLException e) { + Logger.error(e); + } + } + + public void LOAD_ANIMATIONS(ItemBase itemBase) { + + ArrayList tempList = new ArrayList<>(); + ArrayList tempListOff = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_itembase_animations` WHERE `itemBaseUUID` = ?")) { + + preparedStatement.setInt(1, itemBase.getUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + int animation = rs.getInt("animation"); + boolean rightHand = rs.getBoolean("rightHand"); + + if (rightHand) + tempList.add(animation); + else + tempListOff.add(animation); + } + } catch (SQLException e) { + Logger.error(e); + } + + itemBase.setAnimations(tempList); + itemBase.setOffHandAnimations(tempListOff); + } + + public void LOAD_ALL_ITEMBASES() { + + ItemBase itemBase; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_itembase")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + recordsRead++; + itemBase = new ItemBase(rs); + ItemBase.addToCache(itemBase); + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + ItemBase.getUUIDCache().size()); + } + + public HashMap> LOAD_RUNES_FOR_NPC_AND_MOBS() { + + HashMap> runeSets = new HashMap<>(); + int runeSetID; + int runeBaseID; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_npc_runeSet")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + runeSetID = rs.getInt("runeSet"); + runeBaseID = rs.getInt("runeBase"); + + if (runeSets.get(runeSetID) == null) { + ArrayList runeList = new ArrayList<>(); + runeList.add(runeBaseID); + runeSets.put(runeSetID, runeList); + } else { + ArrayList runeList = runeSets.get(runeSetID); + runeList.add(runeSetID); + runeSets.put(runeSetID, runeList); + } + } + + } catch (SQLException e) { + Logger.error(e); + return runeSets; + } + + Logger.info("read: " + recordsRead + " cached: " + runeSets.size()); + return runeSets; + } + +} diff --git a/src/engine/db/handlers/dbItemHandler.java b/src/engine/db/handlers/dbItemHandler.java new file mode 100644 index 00000000..8e6ffd95 --- /dev/null +++ b/src/engine/db/handlers/dbItemHandler.java @@ -0,0 +1,499 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum.ItemContainerType; +import engine.Enum.ItemType; +import engine.gameManager.DbManager; +import engine.objects.AbstractCharacter; +import engine.objects.CharacterItemManager; +import engine.objects.Item; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; + + +public class dbItemHandler extends dbHandlerBase { + + public dbItemHandler() { + this.localClass = Item.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + private static String formatTradeString(HashSet list) { + int size = list.size(); + + String ret = ""; + + if (size == 0) + return ret; + + boolean start = true; + + for (int i : list) { + if (start) { + ret += i; + start = false; + } else + ret += "," + i; + } + return ret; + } + + public Item ADD_ITEM(Item toAdd) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `item_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?,?);")) { + + preparedStatement.setInt(1, toAdd.getOwnerID()); + preparedStatement.setInt(2, toAdd.getItemBaseID()); + preparedStatement.setInt(3, toAdd.getChargesRemaining()); + preparedStatement.setInt(4, toAdd.getDurabilityCurrent()); + preparedStatement.setInt(5, toAdd.getDurabilityMax()); + + if (toAdd.getNumOfItems() < 1) + preparedStatement.setInt(6, 1); + else + preparedStatement.setInt(6, toAdd.getNumOfItems()); + + switch (toAdd.containerType) { + case INVENTORY: + preparedStatement.setString(7, "inventory"); + break; + case EQUIPPED: + preparedStatement.setString(7, "equip"); + break; + case BANK: + preparedStatement.setString(7, "bank"); + break; + case VAULT: + preparedStatement.setString(7, "vault"); + break; + case FORGE: + preparedStatement.setString(7, "forge"); + break; + default: + preparedStatement.setString(7, "none"); //Shouldn't be here + break; + } + + preparedStatement.setByte(8, toAdd.getEquipSlot()); + preparedStatement.setInt(9, toAdd.getFlags()); + preparedStatement.setString(10, toAdd.getCustomName()); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + int objectUUID = (int) rs.getLong("UID"); + + if (objectUUID > 0) + return GET_ITEM(objectUUID); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return null; + } + + public boolean DO_TRADE(HashSet from1, HashSet from2, + CharacterItemManager man1, CharacterItemManager man2, + Item inventoryGold1, Item inventoryGold2, int goldFrom1, int goldFrom2) { + + AbstractCharacter ac1 = man1.getOwner(); + AbstractCharacter ac2 = man2.getOwner(); + boolean worked = false; + + if (ac1 == null || ac2 == null || inventoryGold1 == null || inventoryGold2 == null) + return false; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `item_TRADE`(?, ?, ?, ?, ?, ?, ?, ?)")) { + + preparedStatement.setString(1, formatTradeString(from1)); + preparedStatement.setLong(2, ac1.getObjectUUID()); + preparedStatement.setString(3, formatTradeString(from2)); + preparedStatement.setLong(4, ac2.getObjectUUID()); + preparedStatement.setInt(5, goldFrom1); + preparedStatement.setLong(6, inventoryGold1.getObjectUUID()); + preparedStatement.setInt(7, goldFrom2); + preparedStatement.setLong(8, inventoryGold2.getObjectUUID()); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + worked = rs.getBoolean("result"); + + } catch (SQLException e) { + Logger.error(e); + } + return worked; + } + + public ArrayList GET_EQUIPPED_ITEMS(final int targetId) { + + ArrayList itemList; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=? && `obj_item`.`item_container`='equip';")) { + + preparedStatement.setLong(1, targetId); + ResultSet rs = preparedStatement.executeQuery(); + + itemList = getObjectsFromRs(rs, 10); + + } catch (SQLException e) { + Logger.error(e); + return null; + } + + return itemList; + } + + public Item GET_ITEM(final int itemUUID) { + + Item item; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`UID`=?;")) { + + preparedStatement.setLong(1, itemUUID); + ResultSet rs = preparedStatement.executeQuery(); + + item = (Item) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + return null; + } + return item; + } + + public ArrayList GET_ITEMS_FOR_ACCOUNT(final int accountId) { + + ArrayList itemList; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=?;")) { + + preparedStatement.setLong(1, accountId); + ResultSet rs = preparedStatement.executeQuery(); + + itemList = getObjectsFromRs(rs, 100); + + } catch (SQLException e) { + Logger.error(e); + return null; + } + return itemList; + } + + public ArrayList GET_ITEMS_FOR_NPC(final int npcId) { + + ArrayList itemList; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=?;")) { + + preparedStatement.setLong(1, npcId); + ResultSet rs = preparedStatement.executeQuery(); + + itemList = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + return null; + } + return itemList; + } + + public ArrayList GET_ITEMS_FOR_PC(final int id) { + + ArrayList itemList; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=?")) { + + preparedStatement.setLong(1, id); + ResultSet rs = preparedStatement.executeQuery(); + + itemList = getObjectsFromRs(rs, 100); + + } catch (SQLException e) { + Logger.error(e); + return null; + } + return itemList; + } + + public boolean MOVE_GOLD(final Item from, final Item to, final int amt) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_numberOfItems` = CASE WHEN `UID`=? THEN ? WHEN `UID`=? THEN ? END WHERE `UID` IN (?, ?);")) { + + int newFromAmt = from.getNumOfItems() - amt; + int newToAmt = to.getNumOfItems() + amt; + + preparedStatement.setLong(1, from.getObjectUUID()); + preparedStatement.setInt(2, newFromAmt); + preparedStatement.setLong(3, to.getObjectUUID()); + preparedStatement.setInt(4, newToAmt); + preparedStatement.setLong(5, from.getObjectUUID()); + preparedStatement.setLong(6, to.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean ORPHAN_INVENTORY(final HashSet inventory) { + + boolean worked = true; + + for (Item item : inventory) { + + if (item.getItemBase().getType().equals(ItemType.GOLD)) + continue; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` LEFT JOIN `object` ON `object`.`UID` = `obj_item`.`UID` SET `object`.`parent`=NULL, `obj_item`.`item_container`='none' WHERE `object`.`UID`=?;")) { + + preparedStatement.setLong(1, item.getObjectUUID()); + worked = (preparedStatement.executeUpdate() > 0); + + if (worked) + item.zeroItem(); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + return worked; + } + + public HashSet GET_ITEMS_FOR_VENDOR(final int vendorID) { + + HashSet itemSet = new HashSet<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT ID FROM static_itembase WHERE vendorType = ?")) { + + preparedStatement.setInt(1, vendorID); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + itemSet.add(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + return itemSet; + } + + return itemSet; + } + + //Used to transfer a single item between owners or equip or vault or bank or inventory + public boolean UPDATE_OWNER(final Item item, int newOwnerID, boolean ownerNPC, boolean ownerPlayer, + boolean ownerAccount, ItemContainerType containerType, int slot) { + + boolean worked = false; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `item_TRANSFER_OWNER`(?, ?, ?, ? )")) { + + preparedStatement.setLong(1, item.getObjectUUID()); + + if (newOwnerID != 0) + preparedStatement.setLong(2, newOwnerID); + else + preparedStatement.setNull(2, java.sql.Types.BIGINT); + + switch (containerType) { + case INVENTORY: + preparedStatement.setString(3, "inventory"); + break; + case EQUIPPED: + preparedStatement.setString(3, "equip"); + break; + case BANK: + preparedStatement.setString(3, "bank"); + break; + case VAULT: + preparedStatement.setString(3, "vault"); + break; + case FORGE: + preparedStatement.setString(3, "forge"); + break; + default: + preparedStatement.setString(3, "none"); //Shouldn't be here + break; + } + preparedStatement.setInt(4, slot); + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + worked = rs.getBoolean("result"); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + return worked; + } + + public boolean SET_DURABILITY(final Item item, int value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_durabilityCurrent`=? WHERE `UID`=? AND `item_durabilityCurrent`=?")) { + + preparedStatement.setInt(1, value); + preparedStatement.setLong(2, item.getObjectUUID()); + preparedStatement.setInt(3, item.getDurabilityCurrent()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_FORGE_TO_INVENTORY(final Item item) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_container` = ? WHERE `UID` = ? AND `item_container` = 'forge';")) { + + preparedStatement.setString(1, "inventory"); + preparedStatement.setLong(2, item.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + /** + * Attempts to update the quantity of this gold item + * + * @param value New quantity of gold + * @return True on success + */ + public boolean UPDATE_GOLD(final Item item, int value) { + if (item == null) + return false; + return UPDATE_GOLD(item, value, item.getNumOfItems()); + } + + /** + * Attempts to update the quantity of this gold item using CAS + * + * @return True on success + */ + public boolean UPDATE_GOLD(final Item item, int newValue, int oldValue) { + + if (!item.getItemBase().getType().equals(ItemType.GOLD)) + return false; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_numberOfItems`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, newValue); + preparedStatement.setLong(2, item.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + public boolean UPDATE_REMAINING_CHARGES(final Item item) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_chargesRemaining` = ? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, item.getChargesRemaining()); + preparedStatement.setLong(2, item.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + // This is necessary because default number of items is 1. + // When we create gold, we want it to start at 0 quantity. + + public boolean ZERO_ITEM_STACK(Item item) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_numberOfItems`=0 WHERE `UID` = ?")) { + + preparedStatement.setLong(1, item.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_FLAGS(Item item) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_flags`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, item.getFlags()); + preparedStatement.setLong(2, item.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_VALUE(Item item, int value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_item` SET `item_value`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, value); + preparedStatement.setLong(2, item.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } +} diff --git a/src/engine/db/handlers/dbKitHandler.java b/src/engine/db/handlers/dbKitHandler.java new file mode 100644 index 00000000..b23e94a6 --- /dev/null +++ b/src/engine/db/handlers/dbKitHandler.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Kit; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbKitHandler extends dbHandlerBase { + + public dbKitHandler() { + this.localClass = Kit.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList GET_ALL_KITS() { + + ArrayList kitList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_validkit`")) { + + ResultSet rs = preparedStatement.executeQuery(); + kitList = getObjectsFromRs(rs, 20); + + } catch (SQLException e) { + Logger.error(e); + } + + return kitList; + } +} diff --git a/src/engine/db/handlers/dbLootHandler.java b/src/engine/db/handlers/dbLootHandler.java new file mode 100644 index 00000000..14c4bb99 --- /dev/null +++ b/src/engine/db/handlers/dbLootHandler.java @@ -0,0 +1,240 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.loot.*; +import engine.objects.Item; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class dbLootHandler extends dbHandlerBase { + + public dbLootHandler() { + + } + + public HashMap> LOAD_GEN_ITEM_TABLES() { + + HashMap> genTables = new HashMap<>(); + GenTableEntry genTableEntry; + + int genTableID; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_loot_gen`")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + genTableID = rs.getInt("genTable"); + genTableEntry = new GenTableEntry(rs); + + if (genTables.get(genTableID) == null) { + ArrayList genItemList = new ArrayList<>(); + genItemList.add(genTableEntry); + genTables.put(genTableID, genItemList); + } else { + ArrayList genItemList = genTables.get(genTableID); + genItemList.add(genTableEntry); + genTables.put(genTableID, genItemList); + } + } + } catch (SQLException e) { + Logger.error(e); + return genTables; + } + + Logger.info("read: " + recordsRead + " cached: " + genTables.size()); + return genTables; + } + + public HashMap> LOAD_ITEM_TABLES() { + + HashMap> itemTables = new HashMap<>(); + ItemTableEntry itemTableEntry; + + int itemTableID; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_loot_item`")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + itemTableID = rs.getInt("itemTable"); + itemTableEntry = new ItemTableEntry(rs); + + if (itemTables.get(itemTableID) == null) { + ArrayList itemTableList = new ArrayList<>(); + itemTableList.add(itemTableEntry); + itemTables.put(itemTableID, itemTableList); + } else { + ArrayList itemTableList = itemTables.get(itemTableID); + itemTableList.add(itemTableEntry); + itemTables.put(itemTableID, itemTableList); + } + } + } catch (SQLException e) { + Logger.error(e); + return itemTables; + } + + Logger.info("read: " + recordsRead + " cached: " + itemTables.size()); + return itemTables; + } + + public HashMap> LOAD_MOD_TABLES() { + + HashMap> modTables = new HashMap<>(); + ModTableEntry modTableEntry; + + int modTableID; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_loot_mod`")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + modTableID = rs.getInt("modTable"); + modTableEntry = new ModTableEntry(rs); + + if (modTables.get(modTableID) == null) { + ArrayList modTableList = new ArrayList<>(); + modTableList.add(modTableEntry); + modTables.put(modTableID, modTableList); + } else { + ArrayList modTableList = modTables.get(modTableID); + modTableList.add(modTableEntry); + modTables.put(modTableID, modTableList); + } + } + } catch (SQLException e) { + Logger.error(e); + return modTables; + } + + Logger.info("read: " + recordsRead + " cached: " + modTables.size()); + return modTables; + } + + public HashMap> LOAD_MOD_TYPE_TABLES() { + + HashMap> modTypeTables = new HashMap<>(); + ModTypeTableEntry modTypeTableEntry; + + int modTableID; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_loot_modtype`")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + modTableID = rs.getInt("modType"); + modTypeTableEntry = new ModTypeTableEntry(rs); + + if (modTypeTables.get(modTableID) == null) { + ArrayList modTypeTableList = new ArrayList<>(); + modTypeTableList.add(modTypeTableEntry); + modTypeTables.put(modTableID, modTypeTableList); + } else { + ArrayList modTypeTableList = modTypeTables.get(modTableID); + modTypeTableList.add(modTypeTableEntry); + modTypeTables.put(modTableID, modTypeTableList); + } + } + } catch (SQLException e) { + Logger.error(e); + return modTypeTables; + } + + Logger.info("read: " + recordsRead + " cached: " + modTypeTables.size()); + return modTypeTables; + } + + public HashMap> LOAD_BOOTY_TABLES() { + + HashMap> bootySets = new HashMap<>(); + BootySetEntry bootySetEntry; + int bootySetID; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_loot_bootySet")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + bootySetID = rs.getInt("bootySet"); + bootySetEntry = new BootySetEntry(rs); + + if (bootySets.get(bootySetID) == null) { + ArrayList bootyList = new ArrayList<>(); + bootyList.add(bootySetEntry); + bootySets.put(bootySetID, bootyList); + } else { + ArrayList bootyList = bootySets.get(bootySetID); + bootyList.add(bootySetEntry); + bootySets.put(bootySetID, bootyList); + } + } + } catch (SQLException e) { + Logger.error(e); + return bootySets; + } + + Logger.info("read: " + recordsRead + " cached: " + bootySets.size()); + return bootySets; + } + + public void LOAD_ENCHANT_VALUES() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `IDString`, `minMod` FROM `static_power_effectmod` WHERE `modType` = ?")) { + + preparedStatement.setString(1, "Value"); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + Item.addEnchantValue(rs.getString("IDString"), rs.getInt("minMod")); + + } catch (SQLException e) { + Logger.error(e); + } + } + +} diff --git a/src/engine/db/handlers/dbMenuHandler.java b/src/engine/db/handlers/dbMenuHandler.java new file mode 100644 index 00000000..959b0e23 --- /dev/null +++ b/src/engine/db/handlers/dbMenuHandler.java @@ -0,0 +1,47 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.MenuOption; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbMenuHandler extends dbHandlerBase { + + public dbMenuHandler() { + this.localClass = MenuOption.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList GET_MENU_OPTIONS(final int id) { + + ArrayList menuOptions = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_menuoption` WHERE menuID = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + menuOptions = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + + return menuOptions; + } +} diff --git a/src/engine/db/handlers/dbMineHandler.java b/src/engine/db/handlers/dbMineHandler.java new file mode 100644 index 00000000..da722779 --- /dev/null +++ b/src/engine/db/handlers/dbMineHandler.java @@ -0,0 +1,135 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.Mine; +import engine.objects.MineProduction; +import engine.objects.Resource; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbMineHandler extends dbHandlerBase { + + public dbMineHandler() { + this.localClass = Mine.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public Mine GET_MINE(int id) { + + if (id == 0) + return null; + + Mine mine = (Mine) DbManager.getFromCache(Enum.GameObjectType.Mine, id); + + if (mine != null) + return mine; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_building`.*, `object`.`parent` FROM `object` INNER JOIN `obj_building` ON `obj_building`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;")) { + + preparedStatement.setLong(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + mine = (Mine) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return mine; + } + + public ArrayList GET_ALL_MINES_FOR_SERVER() { + + ArrayList mines = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_mine`.*, `object`.`parent` FROM `object` INNER JOIN `obj_mine` ON `obj_mine`.`UID` = `object`.`UID`")) { + + ResultSet rs = preparedStatement.executeQuery(); + mines = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + return mines; + } + + public boolean CHANGE_OWNER(Mine mine, int playerUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_mine` SET `mine_ownerUID`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, playerUID); + preparedStatement.setLong(2, mine.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean CHANGE_RESOURCE(Mine mine, Resource resource) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_mine` SET `mine_resource`=? WHERE `UID`=?")) { + + preparedStatement.setString(1, resource.name()); + preparedStatement.setLong(2, mine.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean CHANGE_TYPE(Mine mine, MineProduction productionType) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_mine` SET `mine_type`=? WHERE `UID`=?")) { + + preparedStatement.setString(1, productionType.name()); + preparedStatement.setLong(2, mine.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_FLAGS(Mine mine, int newFlags) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_mine` SET `flags`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, newFlags); + preparedStatement.setLong(2, mine.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + +} diff --git a/src/engine/db/handlers/dbMobBaseHandler.java b/src/engine/db/handlers/dbMobBaseHandler.java new file mode 100644 index 00000000..0337ff76 --- /dev/null +++ b/src/engine/db/handlers/dbMobBaseHandler.java @@ -0,0 +1,138 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.objects.MobBase; +import engine.objects.MobBaseEffects; +import engine.objects.MobBaseStats; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbMobBaseHandler extends dbHandlerBase { + + public dbMobBaseHandler() { + this.localClass = MobBase.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public MobBase GET_MOBBASE(int id) { + + if (id == 0) + return null; + + MobBase mobBase = (MobBase) DbManager.getFromCache(GameObjectType.MobBase, id); + + if (mobBase != null) + return mobBase; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase` WHERE `ID`=?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + mobBase = (MobBase) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return mobBase; + } + + + public ArrayList GET_ALL_MOBBASES() { + + ArrayList mobbaseList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + mobbaseList = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + return mobbaseList; + } + + public ArrayList GET_RUNEBASE_EFFECTS(int runeID) { + + ArrayList effectsList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase_effects` WHERE `mobbaseUUID` = ?")) { + + preparedStatement.setInt(1, runeID); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + MobBaseEffects mbs = new MobBaseEffects(rs); + effectsList.add(mbs); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return effectsList; + } + + public MobBaseStats LOAD_STATS(int mobBaseUUID) { + + MobBaseStats mobBaseStats = MobBaseStats.GetGenericStats(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase_stats` WHERE `mobbaseUUID` = ?")) { + + preparedStatement.setInt(1, mobBaseUUID); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + mobBaseStats = new MobBaseStats(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return mobBaseStats; + } + + public void LOAD_ALL_MOBBASE_SPEEDS(MobBase mobBase) { + + if (mobBase.getLoadID() == 0) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase_race` WHERE `mobbaseID` = ?")) { + + preparedStatement.setInt(1, mobBase.getLoadID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + float walk = rs.getFloat("walkStandard"); + float walkCombat = rs.getFloat("walkCombat"); + float run = rs.getFloat("runStandard"); + float runCombat = rs.getFloat("runCombat"); + mobBase.updateSpeeds(walk, walkCombat, run, runCombat); + } + + } catch (SQLException e) { + Logger.error(e); + } + } +} diff --git a/src/engine/db/handlers/dbMobHandler.java b/src/engine/db/handlers/dbMobHandler.java new file mode 100644 index 00000000..f4c5b854 --- /dev/null +++ b/src/engine/db/handlers/dbMobHandler.java @@ -0,0 +1,250 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Mob; +import engine.objects.Zone; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbMobHandler extends dbHandlerBase { + + public dbMobHandler() { + this.localClass = Mob.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public Mob ADD_MOB(Mob toAdd) { + + Mob mobile = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `mob_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")) { + + preparedStatement.setLong(1, toAdd.getParentZoneID()); + preparedStatement.setInt(2, toAdd.getMobBaseID()); + preparedStatement.setInt(3, toAdd.getGuildUUID()); + preparedStatement.setFloat(4, toAdd.getSpawnX()); + preparedStatement.setFloat(5, toAdd.getSpawnY()); + preparedStatement.setFloat(6, toAdd.getSpawnZ()); + preparedStatement.setInt(7, 0); + preparedStatement.setFloat(8, toAdd.getSpawnRadius()); + preparedStatement.setInt(9, toAdd.getTrueSpawnTime()); + + if (toAdd.getContract() != null) + preparedStatement.setInt(10, toAdd.getContract().getContractID()); + else + preparedStatement.setInt(10, 0); + + preparedStatement.setInt(11, toAdd.getBuildingID()); + preparedStatement.setInt(12, toAdd.getLevel()); + preparedStatement.setString(13, toAdd.getFirstName()); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + int objectUUID = (int) rs.getLong("UID"); + + if (objectUUID > 0) + mobile = GET_MOB(objectUUID); + } + + } catch (SQLException e) { + throw new RuntimeException(e); + } + return mobile; + } + + public boolean updateUpgradeTime(Mob mob, DateTime upgradeDateTime) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_mob SET upgradeDate=? " + + "WHERE UID = ?")) { + + if (upgradeDateTime == null) + preparedStatement.setNull(1, java.sql.Types.DATE); + else + preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.getMillis())); + + preparedStatement.setInt(2, mob.getObjectUUID()); + + preparedStatement.execute(); + return true; + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public int DELETE_MOB(final Mob mob) { + + int row_count = 0; + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) { + + preparedStatement.setLong(1, mob.getDBID()); + row_count = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + + } + return row_count; + } + + public void LOAD_PATROL_POINTS(Mob captain) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_guards` WHERE `captainUID` = ?")) { + + preparedStatement.setInt(1, captain.getObjectUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + String name = rs.getString("name"); + Mob toCreate = Mob.createGuardMob(captain, captain.getGuild(), captain.getParentZone(), captain.building.getLoc(), captain.getLevel(), name); + + if (toCreate == null) + return; + + if (toCreate != null) + toCreate.setDeathTime(System.currentTimeMillis()); + + } + + } catch (SQLException e) { + Logger.error(e); + } + } + + public boolean ADD_TO_GUARDS(final long captainUID, final int mobBaseID, final String name, final int slot) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_guards` (`captainUID`, `mobBaseID`,`name`, `slot`) VALUES (?,?,?,?)")) { + + preparedStatement.setLong(1, captainUID); + preparedStatement.setInt(2, mobBaseID); + preparedStatement.setString(3, name); + preparedStatement.setInt(4, slot); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean REMOVE_FROM_GUARDS(final long captainUID, final int mobBaseID, final int slot) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_guards` WHERE `captainUID`=? AND `mobBaseID`=? AND `slot` =?")) { + + preparedStatement.setLong(1, captainUID); + preparedStatement.setInt(2, mobBaseID); + preparedStatement.setInt(3, slot); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + + public ArrayList GET_ALL_MOBS_FOR_ZONE(Zone zone) { + + ArrayList mobileList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_mob`.*, `object`.`parent` FROM `object` INNER JOIN `obj_mob` ON `obj_mob`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;")) { + + preparedStatement.setLong(1, zone.getObjectUUID()); + + ResultSet rs = preparedStatement.executeQuery(); + mobileList = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + + return mobileList; + } + + public Mob GET_MOB(final int objectUUID) { + + Mob mobile = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_mob`.*, `object`.`parent` FROM `object` INNER JOIN `obj_mob` ON `obj_mob`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;")) { + + preparedStatement.setLong(1, objectUUID); + ResultSet rs = preparedStatement.executeQuery(); + mobile = (Mob) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return mobile; + } + + public int MOVE_MOB(long mobID, long parentID, float locX, float locY, float locZ) { + + int row_count = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` INNER JOIN `obj_mob` On `object`.`UID` = `obj_mob`.`UID` SET `object`.`parent`=?, `obj_mob`.`mob_spawnX`=?, `obj_mob`.`mob_spawnY`=?, `obj_mob`.`mob_spawnZ`=? WHERE `obj_mob`.`UID`=?;")) { + + preparedStatement.setLong(1, parentID); + preparedStatement.setFloat(2, locX); + preparedStatement.setFloat(3, locY); + preparedStatement.setFloat(4, locZ); + preparedStatement.setLong(5, mobID); + + ResultSet rs = preparedStatement.executeQuery(); + row_count = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + } + return row_count; + } + + public String SET_PROPERTY(final Mob m, String name, Object new_value) { + + String result = ""; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL mob_SETPROP(?,?,?)")) { + + preparedStatement.setLong(1, m.getObjectUUID()); + preparedStatement.setString(2, name); + preparedStatement.setString(3, String.valueOf(new_value)); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + result = rs.getString("result"); + + } catch (SQLException e) { + Logger.error(e); + } + return result; + } + +} diff --git a/src/engine/db/handlers/dbNPCHandler.java b/src/engine/db/handlers/dbNPCHandler.java new file mode 100644 index 00000000..51b20246 --- /dev/null +++ b/src/engine/db/handlers/dbNPCHandler.java @@ -0,0 +1,470 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum.ProfitType; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.objects.NPC; +import engine.objects.NPCProfits; +import engine.objects.ProducedItem; +import engine.objects.Zone; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbNPCHandler extends dbHandlerBase { + + public dbNPCHandler() { + this.localClass = NPC.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public NPC PERSIST(NPC toAdd) { + + NPC npc = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `npc_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")) { + + Vector3fImmutable bindLocation; + + if (toAdd.buildingUUID == 0) + bindLocation = toAdd.bindLoc; + else + bindLocation = Vector3fImmutable.ZERO; + + preparedStatement.setLong(1, toAdd.parentZoneUUID); + preparedStatement.setString(2, toAdd.getName()); + preparedStatement.setInt(3, toAdd.contractUUID); + preparedStatement.setInt(4, toAdd.guildUUID); + preparedStatement.setFloat(5, bindLocation.x); + preparedStatement.setFloat(6, bindLocation.y); + preparedStatement.setFloat(7, bindLocation.z); + preparedStatement.setInt(8, toAdd.level); + preparedStatement.setFloat(9, toAdd.buyPercent); + preparedStatement.setFloat(10, toAdd.sellPercent); + + preparedStatement.setInt(11, Math.max(toAdd.buildingUUID, 0)); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + int objectUUID = (int) rs.getLong("UID"); + + if (objectUUID > 0) + npc = GET_NPC(objectUUID); + } + + } catch (SQLException e) { + throw new RuntimeException(e); + } + + return npc; + } + + public int DELETE_NPC(final NPC npc) { + + int row_count = 0; + + npc.removeFromZone(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) { + + preparedStatement.setLong(1, npc.getDBID()); + row_count = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + + } + return row_count; + } + + public ArrayList GET_ALL_NPCS_FOR_ZONE(Zone zone) { + + ArrayList npcList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_npc`.*, `object`.`parent` FROM `object` INNER JOIN `obj_npc` ON `obj_npc`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;")) { + + preparedStatement.setLong(1, zone.getObjectUUID()); + + ResultSet rs = preparedStatement.executeQuery(); + npcList = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + + return npcList; + } + + public NPC GET_NPC(final int objectUUID) { + + NPC npc = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_npc`.*, `object`.`parent` FROM `object` INNER JOIN `obj_npc` ON `obj_npc`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;")) { + + preparedStatement.setLong(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + npc = (NPC) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return npc; + } + + public int MOVE_NPC(long npcID, long parentID, float locX, float locY, float locZ) { + + int rowCount; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` INNER JOIN `obj_npc` On `object`.`UID` = `obj_npc`.`UID` SET `object`.`parent`=?, `obj_npc`.`npc_spawnX`=?, `obj_npc`.`npc_spawnY`=?, `obj_npc`.`npc_spawnZ`=? WHERE `obj_npc`.`UID`=?;")) { + + preparedStatement.setLong(1, parentID); + preparedStatement.setFloat(2, locX); + preparedStatement.setFloat(3, locY); + preparedStatement.setFloat(4, locZ); + preparedStatement.setLong(5, npcID); + + rowCount = preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + return 0; + } + return rowCount; + } + + + public String SET_PROPERTY(final NPC n, String name, Object new_value) { + + String result = ""; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL npc_SETPROP(?,?,?)")) { + + preparedStatement.setLong(1, n.getDBID()); + preparedStatement.setString(2, name); + preparedStatement.setString(3, String.valueOf(new_value)); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + result = rs.getString("result"); + + } catch (SQLException e) { + Logger.error(e); + } + return result; + } + + public void updateDatabase(final NPC npc) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_npc SET npc_name=?, npc_contractID=?, npc_typeID=?, npc_guildID=?," + + " npc_spawnX=?, npc_spawnY=?, npc_spawnZ=?, npc_level=? ," + + " npc_buyPercent=?, npc_sellPercent=?, npc_buildingID=? WHERE UID = ?")) { + + preparedStatement.setString(1, npc.getName()); + preparedStatement.setInt(2, (npc.getContract() != null) ? npc.getContract().getObjectUUID() : 0); + preparedStatement.setInt(3, 0); + preparedStatement.setInt(4, (npc.getGuild() != null) ? npc.getGuild().getObjectUUID() : 0); + preparedStatement.setFloat(5, npc.getBindLoc().x); + preparedStatement.setFloat(6, npc.getBindLoc().y); + preparedStatement.setFloat(7, npc.getBindLoc().z); + preparedStatement.setShort(8, npc.getLevel()); + preparedStatement.setFloat(9, npc.getBuyPercent()); + preparedStatement.setFloat(10, npc.getSellPercent()); + preparedStatement.setInt(11, (npc.getBuilding() != null) ? npc.getBuilding().getObjectUUID() : 0); + preparedStatement.setInt(12, npc.getDBID()); + + preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public boolean updateUpgradeTime(NPC npc, DateTime upgradeDateTime) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_npc SET upgradeDate=? " + + "WHERE UID = ?")) { + + if (upgradeDateTime == null) + preparedStatement.setNull(1, java.sql.Types.DATE); + else + preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.getMillis())); + + preparedStatement.setInt(2, npc.getObjectUUID()); + + preparedStatement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_MOBBASE(NPC npc, int mobBaseID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_npc` SET `npc_raceID`=? WHERE `UID`=?")) { + + preparedStatement.setLong(1, mobBaseID); + preparedStatement.setLong(2, npc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_EQUIPSET(NPC npc, int equipSetID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_npc` SET `equipsetID`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, equipSetID); + preparedStatement.setLong(2, npc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_NAME(NPC npc, String name) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_npc` SET `npc_name`=? WHERE `UID`=?")) { + + preparedStatement.setString(1, name); + preparedStatement.setLong(2, npc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public void LOAD_PIRATE_NAMES() { + + String pirateName; + int mobBase; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_piratenames")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + mobBase = rs.getInt("mobbase"); + pirateName = rs.getString("first_name"); + + // Handle new mobbbase entries + + if (NPC._pirateNames.get(mobBase) == null) + NPC._pirateNames.putIfAbsent(mobBase, new ArrayList<>()); + + // Insert name into proper arraylist + + NPC._pirateNames.get(mobBase).add(pirateName); + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("names read: " + recordsRead + " for " + + NPC._pirateNames.size() + " mobBases"); + } + + public boolean ADD_TO_PRODUCTION_LIST(final long ID, final long npcUID, final long itemBaseID, DateTime dateTime, String prefix, String suffix, String name, boolean isRandom, int playerID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_npc_production` (`ID`,`npcUID`, `itemBaseID`,`dateToUpgrade`, `isRandom`, `prefix`, `suffix`, `name`,`playerID`) VALUES (?,?,?,?,?,?,?,?,?)")) { + + preparedStatement.setLong(1, ID); + preparedStatement.setLong(2, npcUID); + preparedStatement.setLong(3, itemBaseID); + preparedStatement.setTimestamp(4, new java.sql.Timestamp(dateTime.getMillis())); + preparedStatement.setBoolean(5, isRandom); + preparedStatement.setString(6, prefix); + preparedStatement.setString(7, suffix); + preparedStatement.setString(8, name); + preparedStatement.setInt(9, playerID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean REMOVE_FROM_PRODUCTION_LIST(final long ID, final long npcUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_npc_production` WHERE `ID`=? AND `npcUID`=?;")) { + + preparedStatement.setLong(1, ID); + preparedStatement.setLong(2, npcUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_ITEM_TO_INVENTORY(final long ID, final long npcUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_npc_production` SET `inForge`=? WHERE `ID`=? AND `npcUID`=?;")) { + + preparedStatement.setByte(1, (byte) 0); + preparedStatement.setLong(2, ID); + preparedStatement.setLong(3, npcUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_ITEM_PRICE(final long ID, final long npcUID, int value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_npc_production` SET `value`=? WHERE `ID`=? AND `npcUID`=?;")) { + + preparedStatement.setInt(1, value); + preparedStatement.setLong(2, ID); + preparedStatement.setLong(3, npcUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public boolean UPDATE_ITEM_ID(final long ID, final long npcUID, final long value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_npc_production` SET `ID`=? WHERE `ID`=? AND `npcUID`=? LIMIT 1;")) { + + preparedStatement.setLong(1, value); + preparedStatement.setLong(2, ID); + preparedStatement.setLong(3, npcUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public void LOAD_ALL_ITEMS_TO_PRODUCE(NPC npc) { + + if (npc == null) + return; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_npc_production` WHERE `npcUID` = ?")) { + + preparedStatement.setInt(1, npc.getObjectUUID()); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + ProducedItem producedItem = new ProducedItem(rs); + npc.forgedItems.add(producedItem); + } + + } catch (SQLException e) { + Logger.error(e); + } + } + + public boolean UPDATE_PROFITS(NPC npc, ProfitType profitType, float value) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_npc_profits` SET `" + profitType.dbField + "` = ? WHERE `npcUID`=?")) { + + preparedStatement.setFloat(1, value); + preparedStatement.setInt(2, npc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } + + public void LOAD_NPC_PROFITS() { + + NPCProfits npcProfit; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM dyn_npc_profits")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + npcProfit = new NPCProfits(rs); + NPCProfits.ProfitCache.put(npcProfit.npcUID, npcProfit); + } + + } catch (SQLException e) { + Logger.error(e); + } + } + + public boolean CREATE_PROFITS(NPC npc) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_npc_profits` (`npcUID`) VALUES (?)")) { + + preparedStatement.setLong(1, npc.getObjectUUID()); + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + } +} diff --git a/src/engine/db/handlers/dbPetitionHandler.java b/src/engine/db/handlers/dbPetitionHandler.java new file mode 100644 index 00000000..23237c01 --- /dev/null +++ b/src/engine/db/handlers/dbPetitionHandler.java @@ -0,0 +1,79 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.Petition; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class dbPetitionHandler extends dbHandlerBase { + + public dbPetitionHandler() { + + } + + public void CREATE_PETITION_TABLE() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CREATE TABLE IF NOT EXISTS dyn_petition " + + " (petitionNumber INT AUTO_INCREMENT NOT NULL," + + " petitionTime DATETIME," + + " primaryType VARCHAR(50)," + + " subType VARCHAR(50)," + + " message VARCHAR(255)," + + " accountID INT," + + " accountName VARCHAR(255)," + + " characterID INT," + + " characterName VARCHAR(255)," + + " location VARCHAR(255), " + + " PRIMARY KEY (petitionNumber))" + + " ENGINE = innodb ROW_FORMAT = DEFAULT;")) { + + preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error(e); + } + } + + public void WRITE_PETITION_TO_TABLE(Petition petition) { + + try (Connection connection = DbManager.getConnection(); + + //check that table exists + + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_petition` (`petitionTime`, `primaryType`, `subType`, `accountID`, `accountName`, `characterID`, `characterName`, `location`, `message`)" + + " VALUES (?,?,?,?,?,?,?,?,?);")) { + + preparedStatement.setTimestamp(1, new java.sql.Timestamp(System.currentTimeMillis())); + preparedStatement.setString(2, Enum.PetitionType.values()[petition.primaryType].name()); + preparedStatement.setString(3, Enum.PetitionSubType.values()[petition.subType].name()); + preparedStatement.setInt(4, petition.reportAccount.getObjectUUID()); + preparedStatement.setString(5, petition.reportAccount.getUname()); + preparedStatement.setInt(6, petition.reportPlayer.getObjectUUID()); + preparedStatement.setString(7, petition.reportPlayer.getFirstName()); + preparedStatement.setString(8, petition.playerLocation.toString()); + + String messageCleaned = petition.message.substring(0, Math.min(petition.message.length(), 255)); + preparedStatement.setString(9, messageCleaned); + + preparedStatement.executeUpdate(); + + } catch (SQLException e) { + Logger.error("CREATE PETITION FAILED: " + e); + } + } + +} diff --git a/src/engine/db/handlers/dbPlayerCharacterHandler.java b/src/engine/db/handlers/dbPlayerCharacterHandler.java new file mode 100644 index 00000000..59b48a78 --- /dev/null +++ b/src/engine/db/handlers/dbPlayerCharacterHandler.java @@ -0,0 +1,571 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.AbstractWorldObject; +import engine.objects.Heraldry; +import engine.objects.PlayerCharacter; +import engine.objects.PlayerFriends; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class dbPlayerCharacterHandler extends dbHandlerBase { + + public dbPlayerCharacterHandler() { + this.localClass = PlayerCharacter.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public PlayerCharacter ADD_PLAYER_CHARACTER(final PlayerCharacter toAdd) { + + PlayerCharacter playerCharacter = null; + + if (toAdd.getAccount() == null) + return null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `character_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")) { + + preparedStatement.setLong(1, toAdd.getAccount().getObjectUUID()); + preparedStatement.setString(2, toAdd.getFirstName()); + preparedStatement.setString(3, toAdd.getLastName()); + preparedStatement.setInt(4, toAdd.getRace().getRaceRuneID()); + preparedStatement.setInt(5, toAdd.getBaseClass().getObjectUUID()); + preparedStatement.setInt(6, toAdd.getStrMod()); + preparedStatement.setInt(7, toAdd.getDexMod()); + preparedStatement.setInt(8, toAdd.getConMod()); + preparedStatement.setInt(9, toAdd.getIntMod()); + preparedStatement.setInt(10, toAdd.getSpiMod()); + preparedStatement.setInt(11, toAdd.getExp()); + preparedStatement.setInt(12, toAdd.getSkinColor()); + preparedStatement.setInt(13, toAdd.getHairColor()); + preparedStatement.setByte(14, toAdd.getHairStyle()); + preparedStatement.setInt(15, toAdd.getBeardColor()); + preparedStatement.setByte(16, toAdd.getBeardStyle()); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) { + int objectUUID = (int) rs.getLong("UID"); + + if (objectUUID > 0) + playerCharacter = GET_PLAYER_CHARACTER(objectUUID); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return playerCharacter; + } + + public boolean SET_IGNORE_LIST(int sourceID, int targetID, boolean toIgnore, String charName) { + + String queryString = ""; + + if (toIgnore) + queryString = "INSERT INTO `dyn_character_ignore` (`accountUID`, `ignoringUID`, `characterName`) VALUES (?, ?, ?)"; + else + queryString = "DELETE FROM `dyn_character_ignore` WHERE `accountUID` = ? && `ignoringUID` = ?"; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(queryString)) { + + preparedStatement.setLong(1, sourceID); + preparedStatement.setLong(2, targetID); + + if (toIgnore) + preparedStatement.setString(3, charName); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public ArrayList GET_CHARACTERS_FOR_ACCOUNT(final int id) { + + ArrayList characterList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_character`.*, `object`.`parent` FROM `object` INNER JOIN `obj_character` ON `obj_character`.`UID` = `object`.`UID` WHERE `object`.`parent`=? && `obj_character`.`char_isActive`='1';")) { + + preparedStatement.setLong(1, id); + ResultSet rs = preparedStatement.executeQuery(); + characterList = getObjectsFromRs(rs, 10); + + } catch (SQLException e) { + Logger.error(e); + } + + return characterList; + } + + public ArrayList GET_ALL_CHARACTERS() { + + ArrayList characterList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_character`.*, `object`.`parent` FROM `object` INNER JOIN `obj_character` ON `obj_character`.`UID` = `object`.`UID` WHERE `obj_character`.`char_isActive`='1';")) { + + ResultSet rs = preparedStatement.executeQuery(); + characterList = getObjectsFromRs(rs, 2000); + + } catch (SQLException e) { + Logger.error(e); + } + + return characterList; + } + + /** + * getFirstName looks up the first name of a PlayerCharacter by + * first checking the GOM cache and then querying the database. + * PlayerCharacter objects that are not already cached won't be instantiated + * and cached. + */ + + public ConcurrentHashMap GET_IGNORE_LIST(final int objectUUID, final boolean skipActiveCheck) { + + ConcurrentHashMap ignoreList = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `dyn_character_ignore` WHERE `accountUID` = ?;")) { + + preparedStatement.setLong(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + int ignoreCharacterID = rs.getInt("ignoringUID"); + + if (ignoreCharacterID == 0) + continue; + + String name = rs.getString("characterName"); + ignoreList.put(ignoreCharacterID, name); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return ignoreList; + } + + public PlayerCharacter GET_PLAYER_CHARACTER(final int objectUUID) { + + if (objectUUID == 0) + return null; + + PlayerCharacter playerCharacter = (PlayerCharacter) DbManager.getFromCache(Enum.GameObjectType.PlayerCharacter, objectUUID); + + if (playerCharacter != null) + return playerCharacter; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_character`.*, `object`.`parent` FROM `object` INNER JOIN `obj_character` ON `obj_character`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?")) { + + preparedStatement.setLong(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + playerCharacter = (PlayerCharacter) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return playerCharacter; + } + + public boolean IS_CHARACTER_NAME_UNIQUE(final String firstName) { + + boolean unique = true; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `char_firstname` FROM `obj_character` WHERE `char_isActive`=1 && `char_firstname`=?")) { + + preparedStatement.setString(1, firstName); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + unique = false; + + } catch (SQLException e) { + Logger.error(e); + } + + return unique; + } + + public boolean UPDATE_NAME(String oldFirstName, String newFirstName, String newLastName) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `char_firstname`=?, `char_lastname`=? WHERE `char_firstname`=? AND `char_isActive`='1'")) { + + preparedStatement.setString(1, newFirstName); + preparedStatement.setString(2, newLastName); + preparedStatement.setString(3, oldFirstName); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_DELETED(final PlayerCharacter pc) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `char_isActive`=? WHERE `UID` = ?")) { + + preparedStatement.setBoolean(1, !pc.isDeleted()); + preparedStatement.setLong(2, pc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_ACTIVE(final PlayerCharacter pc, boolean status) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `char_isActive`=? WHERE `UID` = ?")) { + + preparedStatement.setBoolean(1, status); + preparedStatement.setLong(2, pc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_BIND_BUILDING(final PlayerCharacter pc, int bindBuildingID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `char_bindBuilding`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, bindBuildingID); + preparedStatement.setLong(2, pc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_ANNIVERSERY(final PlayerCharacter pc, boolean flag) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `anniversery`=? WHERE `UID` = ?")) { + + preparedStatement.setBoolean(1, flag); + preparedStatement.setLong(2, pc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + + public boolean UPDATE_CHARACTER_EXPERIENCE(final PlayerCharacter pc) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `char_experience`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, pc.getExp()); + preparedStatement.setLong(2, pc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_GUILD(final PlayerCharacter pc, int guildUUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guildUID`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, guildUUID); + preparedStatement.setLong(2, pc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean UPDATE_CHARACTER_STATS(final PlayerCharacter pc) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `char_strMod`=?, `char_dexMod`=?, `char_conMod`=?, `char_intMod`=?, `char_spiMod`=? WHERE `UID`=?")) { + + preparedStatement.setInt(1, pc.getStrMod()); + preparedStatement.setInt(2, pc.getDexMod()); + preparedStatement.setInt(3, pc.getConMod()); + preparedStatement.setInt(4, pc.getIntMod()); + preparedStatement.setInt(5, pc.getSpiMod()); + preparedStatement.setLong(6, pc.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public String SET_PROPERTY(final PlayerCharacter playerCharacter, String name, Object new_value) { + + String result = ""; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL character_SETPROP(?,?,?)")) { + + preparedStatement.setLong(1, playerCharacter.getObjectUUID()); + preparedStatement.setString(2, name); + preparedStatement.setString(3, String.valueOf(new_value)); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + result = rs.getString("result"); + + } catch (SQLException e) { + Logger.error(e); + } + return result; + } + + public boolean SET_PROMOTION_CLASS(PlayerCharacter player, int promotionClassID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `char_promotionClassID`=? WHERE `UID`=?;")) { + + preparedStatement.setInt(1, promotionClassID); + preparedStatement.setInt(2, player.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_INNERCOUNCIL(PlayerCharacter player, boolean isInnerCouncil) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guild_isInnerCouncil`=? WHERE `UID`=?;")) { + + preparedStatement.setBoolean(1, isInnerCouncil); + preparedStatement.setInt(2, player.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_FULL_MEMBER(PlayerCharacter player, boolean isFullMember) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guild_isFullMember`=? WHERE `UID`=?;")) { + + preparedStatement.setBoolean(1, isFullMember); + preparedStatement.setInt(2, player.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_TAX_COLLECTOR(PlayerCharacter player, boolean isTaxCollector) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guild_isTaxCollector`=? WHERE `UID`=?;")) { + + preparedStatement.setBoolean(1, isTaxCollector); + preparedStatement.setInt(2, player.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_RECRUITER(PlayerCharacter player, boolean isRecruiter) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guild_isRecruiter`=? WHERE `UID`=?;")) { + + preparedStatement.setBoolean(1, isRecruiter); + preparedStatement.setInt(2, player.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean SET_GUILD_TITLE(PlayerCharacter player, int title) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_character` SET `guild_title`=? WHERE `UID`=?;")) { + + preparedStatement.setInt(1, title); + preparedStatement.setInt(2, player.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean ADD_FRIEND(int source, long friend) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_friends` (`playerUID`, `friendUID`) VALUES (?, ?)")) { + + preparedStatement.setLong(1, source); + preparedStatement.setLong(2, friend); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean REMOVE_FRIEND(int source, int friend) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_character_friends` WHERE (`playerUID`=?) AND (`friendUID`=?)")) { + + preparedStatement.setLong(1, source); + preparedStatement.setLong(2, friend); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public void LOAD_PLAYER_FRIENDS() { + + PlayerFriends playerFriend; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM dyn_character_friends")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + playerFriend = new PlayerFriends(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + } + + public boolean ADD_HERALDY(int source, AbstractWorldObject character) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_heraldy` (`playerUID`, `characterUID`,`characterType`) VALUES (?, ?,?)")) { + + preparedStatement.setLong(1, source); + preparedStatement.setLong(2, character.getObjectUUID()); + preparedStatement.setInt(3, character.getObjectType().ordinal()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean REMOVE_HERALDY(int source, int characterUID) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_character_heraldy` WHERE (`playerUID`=?) AND (`characterUID`=?)")) { + + preparedStatement.setLong(1, source); + preparedStatement.setLong(2, characterUID); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public void LOAD_HERALDY() { + + Heraldry heraldy; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM dyn_character_heraldy")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + heraldy = new Heraldry(rs); + + } catch (SQLException e) { + Logger.error(e); + } + } + +} diff --git a/src/engine/db/handlers/dbPowerHandler.java b/src/engine/db/handlers/dbPowerHandler.java new file mode 100644 index 00000000..664da1de --- /dev/null +++ b/src/engine/db/handlers/dbPowerHandler.java @@ -0,0 +1,128 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.objects.Mob; +import engine.objects.PreparedStatementShared; +import engine.powers.EffectsBase; +import engine.powers.MobPowerEntry; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class dbPowerHandler extends dbHandlerBase { + + public dbPowerHandler() { + this.localClass = Mob.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public static void addAllSourceTypes() { + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM static_power_sourcetype"); + ResultSet rs = ps.executeQuery(); + String IDString, source; + while (rs.next()) { + IDString = rs.getString("IDString"); + int token = DbManager.hasher.SBStringHash(IDString); + + + source = rs.getString("source").replace("-", "").trim(); + Enum.EffectSourceType effectSourceType = Enum.EffectSourceType.GetEffectSourceType(source); + + if (EffectsBase.effectSourceTypeMap.containsKey(token) == false) + EffectsBase.effectSourceTypeMap.put(token, new HashSet<>()); + + EffectsBase.effectSourceTypeMap.get(token).add(effectSourceType); + } + rs.close(); + } catch (Exception e) { + Logger.error(e); + } finally { + ps.release(); + } + } + + public static void addAllAnimationOverrides() { + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM static_power_animation_override"); + ResultSet rs = ps.executeQuery(); + String IDString; + int animation; + while (rs.next()) { + IDString = rs.getString("IDString"); + + EffectsBase eb = PowersManager.getEffectByIDString(IDString); + if (eb != null) + IDString = eb.getIDString(); + + animation = rs.getInt("animation"); + PowersManager.AnimationOverrides.put(IDString, animation); + + } + rs.close(); + } catch (Exception e) { + Logger.error(e); + } finally { + ps.release(); + } + } + + public static HashMap> LOAD_MOB_POWERS() { + + HashMap> mobPowers = new HashMap<>(); + MobPowerEntry mobPowerEntry; + + int mobbaseID; + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_npc_mobbase_powers ORDER BY `id` ASC;")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + mobbaseID = rs.getInt("mobbaseUUID"); + mobPowerEntry = new MobPowerEntry(rs); + + if (mobPowers.get(mobbaseID) == null) { + ArrayList powerList = new ArrayList<>(); + powerList.add(mobPowerEntry); + mobPowers.put(mobbaseID, powerList); + } else { + ArrayList powerList = mobPowers.get(mobbaseID); + powerList.add(mobPowerEntry); + mobPowers.put(mobbaseID, powerList); + } + } + } catch (SQLException e) { + Logger.error(e); + return mobPowers; + } + + Logger.info("read: " + recordsRead + " cached: " + mobPowers.size()); + return mobPowers; + } + +} diff --git a/src/engine/db/handlers/dbPromotionClassHandler.java b/src/engine/db/handlers/dbPromotionClassHandler.java new file mode 100644 index 00000000..aef9af8e --- /dev/null +++ b/src/engine/db/handlers/dbPromotionClassHandler.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.PromotionClass; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbPromotionClassHandler extends dbHandlerBase { + + public dbPromotionClassHandler() { + this.localClass = PromotionClass.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList GET_ALLOWED_RUNES(final PromotionClass pc) { + + ArrayList runeList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_promotionrunereq` WHERE `promoID`=?")) { + + preparedStatement.setInt(1, pc.getObjectUUID()); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + runeList.add(rs.getInt("runereqID")); + + } catch (SQLException e) { + Logger.error(e); + + } + return runeList; + } + + public PromotionClass GET_PROMOTION_CLASS(final int objectUUID) { + + PromotionClass promotionClass = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_promotion` WHERE `ID` = ?")) { + + preparedStatement.setInt(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + promotionClass = (PromotionClass) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return promotionClass; + } + + public ArrayList GET_ALL_PROMOTIONS() { + + ArrayList promotionList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_promotion`")) { + + ResultSet rs = preparedStatement.executeQuery(); + promotionList = getObjectsFromRs(rs, 30); + + } catch (SQLException e) { + Logger.error(e); + } + + return promotionList; + } +} diff --git a/src/engine/db/handlers/dbRaceHandler.java b/src/engine/db/handlers/dbRaceHandler.java new file mode 100644 index 00000000..a8c2727c --- /dev/null +++ b/src/engine/db/handlers/dbRaceHandler.java @@ -0,0 +1,163 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Race; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +public class dbRaceHandler extends dbHandlerBase { + + public dbRaceHandler() { + } + + public HashSet BEARD_COLORS_FOR_RACE(final int id) { + + HashSet integerSet = new HashSet<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `color` FROM `static_rune_racebeardcolor` WHERE `RaceID` = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + integerSet.add(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + } + + return integerSet; + } + + public HashSet BEARD_STYLES_FOR_RACE(final int id) { + + HashSet integerSet = new HashSet<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `beardStyle` FROM `static_rune_racebeardstyle` WHERE `RaceID` = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + integerSet.add(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + } + + return integerSet; + } + + public HashSet HAIR_COLORS_FOR_RACE(final int id) { + + HashSet integerSet = new HashSet<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `color` FROM `static_rune_racehaircolor` WHERE `RaceID` = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + integerSet.add(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + } + + return integerSet; + + } + + public HashSet HAIR_STYLES_FOR_RACE(final int id) { + + HashSet integerSet = new HashSet<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `hairStyle` FROM `static_rune_racehairstyle` WHERE `RaceID` = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + integerSet.add(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + } + + return integerSet; + + } + + public HashSet SKIN_COLOR_FOR_RACE(final int id) { + + HashSet integerSet = new HashSet<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `color` FROM `static_rune_raceskincolor` WHERE `RaceID` = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + integerSet.add(rs.getInt(1)); + + } catch (SQLException e) { + Logger.error(e); + } + + return integerSet; + } + + public ConcurrentHashMap LOAD_ALL_RACES() { + + ConcurrentHashMap races; + Race thisRace; + + races = new ConcurrentHashMap<>(); + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_race")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + thisRace = new Race(rs); + + races.put(thisRace.getRaceRuneID(), thisRace); + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + races.size()); + return races; + } +} diff --git a/src/engine/db/handlers/dbRealmHandler.java b/src/engine/db/handlers/dbRealmHandler.java new file mode 100644 index 00000000..fd076505 --- /dev/null +++ b/src/engine/db/handlers/dbRealmHandler.java @@ -0,0 +1,72 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Realm; +import org.pmw.tinylog.Logger; + +import java.sql.*; +import java.util.concurrent.ConcurrentHashMap; + +public class dbRealmHandler extends dbHandlerBase { + + public dbRealmHandler() { + + } + + public ConcurrentHashMap LOAD_ALL_REALMS() { + + ConcurrentHashMap realmList; + Realm thisRealm; + + realmList = new ConcurrentHashMap<>(); + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM obj_realm")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + recordsRead++; + thisRealm = new Realm(rs); + realmList.put(thisRealm.getRealmID(), thisRealm); + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + realmList.size()); + return realmList; + } + + public void REALM_UPDATE(Realm realm) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL realm_UPDATE(?,?,?,?)")) { + + preparedStatement.setInt(1, realm.getRealmID()); + preparedStatement.setInt(2, (realm.getRulingCity() == null) ? 0 : realm.getRulingCity().getObjectUUID()); + preparedStatement.setInt(3, realm.getCharterType()); + + if (realm.ruledSince != null) + preparedStatement.setTimestamp(4, Timestamp.valueOf(realm.ruledSince)); + else + preparedStatement.setNull(4, java.sql.Types.DATE); + + preparedStatement.execute(); + + } catch (SQLException e) { + Logger.error(e); + } + } +} diff --git a/src/engine/db/handlers/dbResistHandler.java b/src/engine/db/handlers/dbResistHandler.java new file mode 100644 index 00000000..7557ba37 --- /dev/null +++ b/src/engine/db/handlers/dbResistHandler.java @@ -0,0 +1,47 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.Resists; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class dbResistHandler extends dbHandlerBase { + + public dbResistHandler() { + + } + + public Resists GET_RESISTS_FOR_MOB(int resistID) { + + Resists resists = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mob_resists` WHERE `ID` = ?;")) { + + preparedStatement.setInt(1, resistID); + + ResultSet rs = preparedStatement.executeQuery(); + + if (rs.next()) + resists = new Resists(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return resists; + } +} diff --git a/src/engine/db/handlers/dbRuneBaseAttributeHandler.java b/src/engine/db/handlers/dbRuneBaseAttributeHandler.java new file mode 100644 index 00000000..536addb5 --- /dev/null +++ b/src/engine/db/handlers/dbRuneBaseAttributeHandler.java @@ -0,0 +1,47 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.RuneBaseAttribute; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbRuneBaseAttributeHandler extends dbHandlerBase { + + public dbRuneBaseAttributeHandler() { + this.localClass = RuneBaseAttribute.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList GET_ATTRIBUTES_FOR_RUNEBASE() { + + ArrayList runeBaseAttributesList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runebaseattribute`")) { + + ResultSet rs = preparedStatement.executeQuery(); + runeBaseAttributesList = getObjectsFromRs(rs, 10); + + } catch (SQLException e) { + Logger.error(e); + } + + return runeBaseAttributesList; + } + + +} diff --git a/src/engine/db/handlers/dbRuneBaseEffectHandler.java b/src/engine/db/handlers/dbRuneBaseEffectHandler.java new file mode 100644 index 00000000..def0f204 --- /dev/null +++ b/src/engine/db/handlers/dbRuneBaseEffectHandler.java @@ -0,0 +1,91 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.RuneBaseEffect; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class dbRuneBaseEffectHandler extends dbHandlerBase { + + public dbRuneBaseEffectHandler() { + this.localClass = RuneBaseEffect.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList GET_EFFECTS_FOR_RUNEBASE(int id) { + + ArrayList runeBaseEffectsList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_baseeffect` WHERE `runeID`=?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + runeBaseEffectsList = getObjectsFromRs(rs, 250); + + } catch (SQLException e) { + Logger.error(e); + } + + return runeBaseEffectsList; + } + + public ArrayList GET_ALL_RUNEBASE_EFFECTS() { + + ArrayList runeBaseEffectsList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_baseeffect`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + runeBaseEffectsList = getObjectsFromRs(rs, 250); + + } catch (SQLException e) { + Logger.error(e); + } + + return runeBaseEffectsList; + } + + //This calls from cache only. Call this AFTER caching all runebase effects; + + public HashMap> LOAD_BASEEFFECTS_FOR_RUNEBASE() { + + HashMap> runeBaseEffectSet; + runeBaseEffectSet = new HashMap<>(); + + for (AbstractGameObject runeBaseEffect : DbManager.getList(GameObjectType.RuneBaseEffect)) { + + int runeBaseID = ((RuneBaseEffect) runeBaseEffect).getRuneBaseID(); + if (runeBaseEffectSet.get(runeBaseID) == null) { + ArrayList runeBaseEffectList = new ArrayList<>(); + runeBaseEffectList.add((RuneBaseEffect) runeBaseEffect); + runeBaseEffectSet.put(runeBaseID, runeBaseEffectList); + } else { + ArrayList runeBaseEffectList = runeBaseEffectSet.get(runeBaseID); + runeBaseEffectList.add((RuneBaseEffect) runeBaseEffect); + runeBaseEffectSet.put(runeBaseID, runeBaseEffectList); + } + } + return runeBaseEffectSet; + } + +} diff --git a/src/engine/db/handlers/dbRuneBaseHandler.java b/src/engine/db/handlers/dbRuneBaseHandler.java new file mode 100644 index 00000000..af66b036 --- /dev/null +++ b/src/engine/db/handlers/dbRuneBaseHandler.java @@ -0,0 +1,179 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.RuneBase; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class dbRuneBaseHandler extends dbHandlerBase { + + public dbRuneBaseHandler() { + this.localClass = RuneBase.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public void GET_RUNE_REQS(final RuneBase rb) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runereq` WHERE `runeID` = ?")) { + + preparedStatement.setInt(1, rb.getObjectUUID()); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + int type = rs.getInt("type"); + + switch (type) { + case 1: + rb.getRace().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed")); + break; + case 2: + rb.getBaseClass().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed")); + break; + case 3: + rb.getPromotionClass().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed")); + break; + case 4: + rb.getDiscipline().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed")); + break; + case 5: + rb.getOverwrite().add(rs.getInt("requiredRuneID")); + break; + case 6: + rb.setLevelRequired(rs.getInt("requiredRuneID")); + break; + } + } + + } catch (SQLException e) { + Logger.error(e); + } + + } + + public RuneBase GET_RUNEBASE(final int id) { + + RuneBase runeBase = null; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runebase` WHERE `ID` = ?")) { + + preparedStatement.setInt(1, id); + + ResultSet rs = preparedStatement.executeQuery(); + runeBase = (RuneBase) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + return runeBase; + } + + public ArrayList LOAD_ALL_RUNEBASES() { + + ArrayList runeBasesList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runebase`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + runeBasesList = getObjectsFromRs(rs, 1000); + + } catch (SQLException e) { + Logger.error(e); + } + + return runeBasesList; + } + + public HashMap> LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS() { + + HashMap> runeSets = new HashMap<>(); + + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_baseclassrune")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + int baseClassID = rs.getInt("BaseClassesID"); + int runeBaseID = rs.getInt("RuneBaseID"); + + if (runeSets.get(baseClassID) == null) { + ArrayList runeList = new ArrayList<>(); + runeList.add(runeBaseID); + runeSets.put(baseClassID, runeList); + } else { + ArrayList runeList = runeSets.get(baseClassID); + runeList.add(runeBaseID); + runeSets.put(baseClassID, runeList); + } + } + + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + runeSets.size()); + + return runeSets; + } + + public HashMap> LOAD_ALLOWED_STARTING_RUNES_FOR_RACE() { + + HashMap> runeSets; + + runeSets = new HashMap<>(); + int recordsRead = 0; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_racerune")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + recordsRead++; + + int raceID = rs.getInt("RaceID"); + int runeBaseID = rs.getInt("RuneBaseID"); + + if (runeSets.get(raceID) == null) { + ArrayList runeList = new ArrayList<>(); + runeList.add(runeBaseID); + runeSets.put(raceID, runeList); + } else { + ArrayList runeList = runeSets.get(raceID); + runeList.add(runeBaseID); + runeSets.put(raceID, runeList); + } + } + } catch (SQLException e) { + Logger.error(e); + } + + Logger.info("read: " + recordsRead + " cached: " + runeSets.size()); + return runeSets; + } +} diff --git a/src/engine/db/handlers/dbRunegateHandler.java b/src/engine/db/handlers/dbRunegateHandler.java new file mode 100644 index 00000000..50b928b0 --- /dev/null +++ b/src/engine/db/handlers/dbRunegateHandler.java @@ -0,0 +1,74 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.Building; +import engine.objects.Portal; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbRunegateHandler extends dbHandlerBase { + + public dbRunegateHandler() { + + } + + public ArrayList GET_RUNEGATE_LIST() { + + ArrayList gateList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT DISTINCT `sourceBuilding` FROM `static_runegate_portals`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) + gateList.add(rs.getInt("sourceBuilding")); + + } catch (SQLException e) { + Logger.error(e); + } + + return gateList; + } + + public ArrayList GET_PORTAL_LIST(int gateUID) { + + ArrayList portalList = new ArrayList<>(); + Building sourceBuilding = (Building) DbManager.getObject(Enum.GameObjectType.Building, gateUID); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_runegate_portals` WHERE `sourceBuilding` = ?;")) { + + preparedStatement.setInt(1, gateUID); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + int targetBuildingID = rs.getInt("targetBuilding"); + Building targetBuilding = (Building) DbManager.getObject(Enum.GameObjectType.Building, targetBuildingID); + Enum.PortalType portalType = Enum.PortalType.valueOf(rs.getString("portalType")); + Portal portal = new Portal(sourceBuilding, portalType, targetBuilding); + portalList.add(portal); + } + + } catch (SQLException e) { + Logger.error(e); + } + return portalList; + } +} diff --git a/src/engine/db/handlers/dbShrineHandler.java b/src/engine/db/handlers/dbShrineHandler.java new file mode 100644 index 00000000..5857372a --- /dev/null +++ b/src/engine/db/handlers/dbShrineHandler.java @@ -0,0 +1,143 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum.ProtectionState; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.Shrine; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbShrineHandler extends dbHandlerBase { + + public dbShrineHandler() { + this.localClass = Shrine.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public static void addObject(ArrayList list, ResultSet rs) throws SQLException { + + String type = rs.getString("type"); + + switch (type) { + case "building": + Building building = new Building(rs); + DbManager.addToCache(building); + list.add(building); + break; + case "shrine": + Shrine shrine = new Shrine(rs); + DbManager.addToCache(shrine); + list.add(shrine); + break; + } + } + + public ArrayList CREATE_SHRINE(int parentZoneID, int OwnerUUID, String name, int meshUUID, + Vector3fImmutable location, float meshScale, int currentHP, + ProtectionState protectionState, int currentGold, int rank, + DateTime upgradeDate, int blueprintUUID, float w, float rotY, String shrineType) { + + ArrayList shrineList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `shrine_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?,?);")) { + + preparedStatement.setInt(1, parentZoneID); + preparedStatement.setInt(2, OwnerUUID); + preparedStatement.setString(3, name); + preparedStatement.setInt(4, meshUUID); + preparedStatement.setFloat(5, location.x); + preparedStatement.setFloat(6, location.y); + preparedStatement.setFloat(7, location.z); + preparedStatement.setFloat(8, meshScale); + preparedStatement.setInt(9, currentHP); + preparedStatement.setString(10, protectionState.name()); + preparedStatement.setInt(11, currentGold); + preparedStatement.setInt(12, rank); + + if (upgradeDate != null) + preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis())); + else + preparedStatement.setNull(13, java.sql.Types.DATE); + + preparedStatement.setInt(14, blueprintUUID); + preparedStatement.setFloat(15, w); + preparedStatement.setFloat(16, rotY); + preparedStatement.setString(17, shrineType); + + preparedStatement.execute(); + + ResultSet rs = preparedStatement.getResultSet(); + + while (rs.next()) + addObject(shrineList, rs); + + while (preparedStatement.getMoreResults()) { + rs = preparedStatement.getResultSet(); + + while (rs.next()) + addObject(shrineList, rs); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return shrineList; + } + + public boolean updateFavors(Shrine shrine, int amount, int oldAmount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_shrine` SET `shrine_favors`=? WHERE `UID` = ? AND `shrine_favors` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setLong(2, shrine.getObjectUUID()); + preparedStatement.setInt(3, oldAmount); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + return false; + } + + } + + public void LOAD_ALL_SHRINES() { + + Shrine shrine; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_shrine`.*, `object`.`parent`, `object`.`type` FROM `object` LEFT JOIN `obj_shrine` ON `object`.`UID` = `obj_shrine`.`UID` WHERE `object`.`type` = 'shrine';")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + shrine = new Shrine(rs); + shrine.getShrineType().addShrineToServerList(shrine); + } + + } catch (SQLException e) { + Logger.error(e); + } + } + +} diff --git a/src/engine/db/handlers/dbSkillBaseHandler.java b/src/engine/db/handlers/dbSkillBaseHandler.java new file mode 100644 index 00000000..3bfdefe6 --- /dev/null +++ b/src/engine/db/handlers/dbSkillBaseHandler.java @@ -0,0 +1,152 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.objects.MaxSkills; +import engine.objects.SkillsBase; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class dbSkillBaseHandler extends dbHandlerBase { + + public dbSkillBaseHandler() { + this.localClass = SkillsBase.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public SkillsBase GET_BASE(final int objectUUID) { + + SkillsBase skillsBase = (SkillsBase) DbManager.getFromCache(GameObjectType.SkillsBase, objectUUID); + + if (skillsBase != null) + return skillsBase; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_skill_skillsbase WHERE ID = ?")) { + + preparedStatement.setInt(1, objectUUID); + ResultSet rs = preparedStatement.executeQuery(); + + skillsBase = (SkillsBase) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + SkillsBase.putInCache(skillsBase); + return skillsBase; + } + + public SkillsBase GET_BASE_BY_NAME(String name) { + + SkillsBase skillsBase = SkillsBase.getFromCache(name); + + if (skillsBase != null) + return skillsBase; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_skill_skillsbase WHERE name = ?")) { + + preparedStatement.setString(1, name); + ResultSet rs = preparedStatement.executeQuery(); + + skillsBase = (SkillsBase) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + SkillsBase.putInCache(skillsBase); + return skillsBase; + } + + public SkillsBase GET_BASE_BY_TOKEN(final int token) { + + SkillsBase skillsBase = SkillsBase.getFromCache(token); + + if (skillsBase != null) + return skillsBase; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_skill_skillsbase WHERE token = ?")) { + + preparedStatement.setInt(1, token); + ResultSet rs = preparedStatement.executeQuery(); + + skillsBase = (SkillsBase) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + SkillsBase.putInCache(skillsBase); + return skillsBase; + } + + public void LOAD_ALL_MAX_SKILLS_FOR_CONTRACT() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_maxskills`")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + MaxSkills maxSKills = new MaxSkills(rs); + + if (MaxSkills.MaxSkillsSet.get(maxSKills.getRuneID()) == null) { + ArrayList newMaxSkillsList = new ArrayList<>(); + newMaxSkillsList.add(maxSKills); + MaxSkills.MaxSkillsSet.put(maxSKills.getRuneID(), newMaxSkillsList); + } else + MaxSkills.MaxSkillsSet.get(maxSKills.getRuneID()).add(maxSKills); + + } + + } catch (SQLException e) { + Logger.error(e); + } + } + + + public void LOAD_ALL_RUNE_SKILLS() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_skill_skillsgranted`")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + + int runeID = rs.getInt("runeID"); + int token = rs.getInt("token"); + int amount = rs.getInt("amount"); + + if (SkillsBase.runeSkillsCache.get(runeID) == null) + SkillsBase.runeSkillsCache.put(runeID, new HashMap<>()); + + SkillsBase.runeSkillsCache.get(runeID).put(token, amount); + } + + } catch (SQLException e) { + Logger.error(e); + } + } + +} diff --git a/src/engine/db/handlers/dbSkillReqHandler.java b/src/engine/db/handlers/dbSkillReqHandler.java new file mode 100644 index 00000000..c2e0830c --- /dev/null +++ b/src/engine/db/handlers/dbSkillReqHandler.java @@ -0,0 +1,98 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.gameManager.DbManager; +import engine.objects.SkillReq; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class dbSkillReqHandler extends dbHandlerBase { + + public dbSkillReqHandler() { + this.localClass = SkillReq.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public static ArrayList getAllPowersBase() { + + ArrayList powerBaseList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_power_powerbase")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + PowersBase toAdd = new PowersBase(rs); + powerBaseList.add(toAdd); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return powerBaseList; + } + + public static void getFailConditions(HashMap powers) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT IDString, type FROM static_power_failcondition where powerOrEffect = 'Power'")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + String type = rs.getString("type"); + String IDString = rs.getString("IDString"); + PowersBase pb = powers.get(IDString); + + if (pb != null) + switch (type) { + case "CastSpell": + pb.cancelOnCastSpell = true; + break; + case "TakeDamage": + pb.cancelOnTakeDamage = true; + break; + } + } + } catch (SQLException e) { + Logger.error(e); + } + } + + public ArrayList GET_REQS_FOR_RUNE(final int objectUUID) { + + ArrayList skillReqsList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_skill_skillreq` WHERE `runeID`=?")) { + + preparedStatement.setInt(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + skillReqsList = getObjectsFromRs(rs, 5); + + } catch (SQLException e) { + Logger.error(e); + } + + return skillReqsList; + } + +} diff --git a/src/engine/db/handlers/dbVendorDialogHandler.java b/src/engine/db/handlers/dbVendorDialogHandler.java new file mode 100644 index 00000000..8bcc40ee --- /dev/null +++ b/src/engine/db/handlers/dbVendorDialogHandler.java @@ -0,0 +1,50 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.objects.VendorDialog; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class dbVendorDialogHandler extends dbHandlerBase { + + public dbVendorDialogHandler() { + this.localClass = VendorDialog.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public VendorDialog GET_VENDORDIALOG(final int objectUUID) { + + VendorDialog vendorDialog = (VendorDialog) DbManager.getFromCache(Enum.GameObjectType.VendorDialog, objectUUID); + + if (vendorDialog != null) + return vendorDialog; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_vendordialog` WHERE `ID`=?")) { + + preparedStatement.setInt(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + vendorDialog = (VendorDialog) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return vendorDialog; + } +} diff --git a/src/engine/db/handlers/dbWarehouseHandler.java b/src/engine/db/handlers/dbWarehouseHandler.java new file mode 100644 index 00000000..677e67eb --- /dev/null +++ b/src/engine/db/handlers/dbWarehouseHandler.java @@ -0,0 +1,555 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum.GameObjectType; +import engine.Enum.ProtectionState; +import engine.Enum.TransactionType; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class dbWarehouseHandler extends dbHandlerBase { + + private static final ConcurrentHashMap columns = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + public dbWarehouseHandler() { + this.localClass = Warehouse.class; + this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + + } + + public static void addObject(ArrayList list, ResultSet rs) throws SQLException { + String type = rs.getString("type"); + switch (type) { + case "building": + Building building = new Building(rs); + DbManager.addToCache(building); + list.add(building); + break; + case "warehouse": + Warehouse warehouse = new Warehouse(rs); + DbManager.addToCache(warehouse); + list.add(warehouse); + break; + } + } + + public ArrayList CREATE_WAREHOUSE(int parentZoneID, int OwnerUUID, String name, int meshUUID, + Vector3fImmutable location, float meshScale, int currentHP, + ProtectionState protectionState, int currentGold, int rank, + DateTime upgradeDate, int blueprintUUID, float w, float rotY) { + + ArrayList warehouseList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("CALL `WAREHOUSE_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?);")) { + + preparedStatement.setInt(1, parentZoneID); + preparedStatement.setInt(2, OwnerUUID); + preparedStatement.setString(3, name); + preparedStatement.setInt(4, meshUUID); + preparedStatement.setFloat(5, location.x); + preparedStatement.setFloat(6, location.y); + preparedStatement.setFloat(7, location.z); + preparedStatement.setFloat(8, meshScale); + preparedStatement.setInt(9, currentHP); + preparedStatement.setString(10, protectionState.name()); + preparedStatement.setInt(11, currentGold); + preparedStatement.setInt(12, rank); + + if (upgradeDate != null) + preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis())); + else + preparedStatement.setNull(13, java.sql.Types.DATE); + + preparedStatement.setInt(14, blueprintUUID); + preparedStatement.setFloat(15, w); + preparedStatement.setFloat(16, rotY); + + preparedStatement.execute(); + ResultSet rs = preparedStatement.getResultSet(); + + while (rs.next()) + addObject(warehouseList, rs); + + while (preparedStatement.getMoreResults()) { + rs = preparedStatement.getResultSet(); + + while (rs.next()) + addObject(warehouseList, rs); + } + } catch (SQLException e) { + Logger.error(e); + } + + return warehouseList; + } + + public boolean updateLocks(final Warehouse wh, long locks) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_locks`=? WHERE `UID` = ?")) { + + preparedStatement.setLong(1, locks); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateGold(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_gold`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateStone(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_stone`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateTruesteel(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_truesteel`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateIron(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_iron`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateAdamant(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_adamant`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateLumber(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_lumber`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateOak(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_oak`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateBronzewood(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_bronzewood`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateMandrake(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_mandrake`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateCoal(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_coal`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateAgate(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_agate`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateDiamond(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_diamond`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateOnyx(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_onyx`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateAzoth(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_azoth`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateOrichalk(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_orichalk`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateAntimony(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_antimony`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateSulfur(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_sulfur`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateQuicksilver(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_quicksilver`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateGalvor(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_galvor`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateWormwood(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_wormwood`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateObsidian(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_obsidian`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateBloodstone(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_bloodstone`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean updateMithril(final Warehouse wh, int amount) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_warehouse` SET `warehouse_mithril`=? WHERE `UID` = ?")) { + + preparedStatement.setInt(1, amount); + preparedStatement.setInt(2, wh.getUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public boolean CREATE_TRANSACTION(int warehouseBuildingID, GameObjectType targetType, int targetUUID, TransactionType transactionType, Resource resource, int amount, DateTime date) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_warehouse_transactions` (`warehouseUID`, `targetType`,`targetUID`, `type`,`resource`,`amount`,`date` ) VALUES (?,?,?,?,?,?,?)")) { + + preparedStatement.setLong(1, warehouseBuildingID); + preparedStatement.setString(2, targetType.name()); + preparedStatement.setLong(3, targetUUID); + preparedStatement.setString(4, transactionType.name()); + preparedStatement.setString(5, resource.name()); + preparedStatement.setInt(6, amount); + preparedStatement.setTimestamp(7, new java.sql.Timestamp(date.getMillis())); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + + public ArrayList GET_TRANSACTIONS_FOR_WAREHOUSE(final int warehouseUUID) { + + ArrayList transactionsList = new ArrayList<>(); + + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM dyn_warehouse_transactions WHERE `warehouseUID` = ?;")) { + + preparedStatement.setInt(1, warehouseUUID); + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + Transaction transactions = new Transaction(rs); + transactionsList.add(transactions); + } + + } catch (SQLException e) { + Logger.error(e); + } + + return transactionsList; + } + + public void LOAD_ALL_WAREHOUSES() { + + Warehouse warehouse; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_warehouse`.*, `object`.`parent`, `object`.`type` FROM `object` LEFT JOIN `obj_warehouse` ON `object`.`UID` = `obj_warehouse`.`UID` WHERE `object`.`type` = 'warehouse';")) { + + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + warehouse = new Warehouse(rs); + warehouse.runAfterLoad(); + warehouse.loadAllTransactions(); + } + + } catch (SQLException e) { + Logger.error(e); + } + } +} diff --git a/src/engine/db/handlers/dbZoneHandler.java b/src/engine/db/handlers/dbZoneHandler.java new file mode 100644 index 00000000..84abc1a7 --- /dev/null +++ b/src/engine/db/handlers/dbZoneHandler.java @@ -0,0 +1,130 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.db.handlers; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector2f; +import engine.objects.Zone; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class dbZoneHandler extends dbHandlerBase { + + public dbZoneHandler() { + this.localClass = Zone.class; + this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName()); + } + + public ArrayList GET_ALL_NODES(Zone zone) { + ArrayList wsmList = new ArrayList<>(); + wsmList.addAll(zone.getNodes()); + if (zone.absX == 0.0f) { + zone.absX = zone.getXCoord(); + } + if (zone.absY == 0.0f) { + zone.absY = zone.getYCoord(); + } + if (zone.absZ == 0.0f) { + zone.absZ = zone.getZCoord(); + } + for (Zone child : zone.getNodes()) { + child.absX = child.getXCoord() + zone.absX; + child.absY = child.getYCoord() + zone.absY; + child.absZ = child.getZCoord() + zone.absZ; + wsmList.addAll(this.GET_ALL_NODES(child)); + } + return wsmList; + } + + public Zone GET_BY_UID(long ID) { + + Zone zone = (Zone) DbManager.getFromCache(Enum.GameObjectType.Zone, (int) ID); + + if (zone != null) + return zone; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_zone`.*, `object`.`parent` FROM `object` INNER JOIN `obj_zone` ON `obj_zone`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;")) { + + preparedStatement.setLong(1, ID); + + ResultSet rs = preparedStatement.executeQuery(); + zone = (Zone) getObjectFromRs(rs); + + } catch (SQLException e) { + Logger.error(e); + } + + return zone; + } + + public ArrayList GET_MAP_NODES(final int objectUUID) { + + ArrayList zoneList = new ArrayList<>(); + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_zone`.*, `object`.`parent` FROM `object` INNER JOIN `obj_zone` ON `obj_zone`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;")) { + + preparedStatement.setLong(1, objectUUID); + + ResultSet rs = preparedStatement.executeQuery(); + zoneList = getObjectsFromRs(rs, 2000); + + } catch (SQLException e) { + Logger.error(e); + } + + return zoneList; + } + + public void LOAD_ZONE_EXTENTS() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_zone_size`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + Vector2f zoneSize = new Vector2f(); + int loadNum = rs.getInt("loadNum"); + zoneSize.x = rs.getFloat("xRadius"); + zoneSize.y = rs.getFloat("zRadius"); + ZoneManager._zone_size_data.put(loadNum, zoneSize); + } + + } catch (SQLException e) { + Logger.error(e); + } + } + + public boolean DELETE_ZONE(final Zone zone) { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ? AND `type` = 'zone'")) { + + preparedStatement.setInt(1, zone.getObjectUUID()); + + return (preparedStatement.executeUpdate() > 0); + + } catch (SQLException e) { + Logger.error(e); + } + + return false; + } + +} diff --git a/src/engine/devcmd/AbstractDevCmd.java b/src/engine/devcmd/AbstractDevCmd.java new file mode 100644 index 00000000..ae2364b6 --- /dev/null +++ b/src/engine/devcmd/AbstractDevCmd.java @@ -0,0 +1,181 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd; + +import engine.Enum.GameObjectType; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.objects.*; + +import java.util.ArrayList; + +public abstract class AbstractDevCmd { + + protected final ArrayList cmdStrings; + private AbstractGameObject tr; + private String rsult; + + public AbstractDevCmd(String cmdString) { + super(); + this.cmdStrings = new ArrayList<>(); + this.addCmdString(cmdString); + this.rsult = ""; + } + + /* + * Misc tools/helpers + */ + protected static Building getTargetAsBuilding(PlayerCharacter pc) { + int targetType = pc.getLastTargetType().ordinal(); + int targetID = pc.getLastTargetID(); + if (targetType == GameObjectType.Building.ordinal()) { + Building b = (Building) DbManager + .getFromCache(GameObjectType.Building, targetID); + if (b == null) { + ChatManager.chatSystemError( + pc, + "Command Failed. Could not find building of ID " + + targetID); + return null; + } + return b; + } else { + return null; + } + } + + protected static Mob getTargetAsMob(PlayerCharacter pc) { + int targetType = pc.getLastTargetType().ordinal(); + int targetID = pc.getLastTargetID(); + if (targetType == GameObjectType.Mob.ordinal()) { + Mob b = Mob.getMob(targetID); + if (b == null) { + ChatManager.chatSystemError(pc, + "Command Failed. Could not find Mob of ID " + targetID); + return null; + } + return b; + } else { + return null; + } + } + + protected static NPC getTargetAsNPC(PlayerCharacter pc) { + int targetType = pc.getLastTargetType().ordinal(); + int targetID = pc.getLastTargetID(); + if (targetType == GameObjectType.NPC.ordinal()) { + NPC b = NPC.getFromCache(targetID); + if (b == null) { + ChatManager.chatSystemError(pc, + "Command Failed. Could not find NPC of ID " + targetID); + return null; + } + return b; + } else { + return null; + } + } + + /** + * This function is called by the DevCmdManager. Method splits argString + * into a String array and then calls the subclass specific _doCmd method. + */ + + public void doCmd(PlayerCharacter pcSender, String argString, + AbstractGameObject target) { + String[] args = argString.split(" "); + + if (pcSender == null) { + return; + } + + if (args.length > 0 && args[0].equalsIgnoreCase("?")) { + this.sendHelp(pcSender); + this.sendUsage(pcSender); + } else { + this.tr = target; + this._doCmd(pcSender, args, target); + } + } + + protected abstract void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target); + + /** + * Returns the string sent to the client that displays how to use this + * command. + */ + + public final String getUsageString() { + return "Usage: " + this._getUsageString(); + } + + protected abstract String _getUsageString(); + + /** + * Returns the string sent to the client that displays what this command + * does. + */ + + public final String getHelpString() { + return this.getMainCmdString() + ": " + this._getHelpString(); + } + + protected abstract String _getHelpString(); + + public final ArrayList getCmdStrings() { + return cmdStrings; + } + + public final String getMainCmdString() { + return this.cmdStrings.get(0); + } + + protected void addCmdString(String cmdString) { + String lowercase = cmdString.toLowerCase(); + this.cmdStrings.add(lowercase); + } + + public AbstractGameObject getTarget() { + return this.tr; + } + + public void setTarget(AbstractGameObject ago) { + this.tr = ago; + } + + public String getResult() { + return this.rsult; + } + + public void setResult(String result) { + this.rsult = result; + } + + /* + * Helper functions + */ + protected void sendUsage(PlayerCharacter pc) { + this.throwbackError(pc, this.getUsageString()); + } + + protected void sendHelp(PlayerCharacter pc) { + this.throwbackError(pc, this.getHelpString()); + } + + protected void throwbackError(PlayerCharacter pc, String msgText) { + ChatManager.chatSystemError(pc, msgText); + } + + protected void throwbackInfo(PlayerCharacter pc, String msgText) { + ChatManager.chatSystemInfo(pc, msgText); + } + +} diff --git a/src/engine/devcmd/cmds/AddBuildingCmd.java b/src/engine/devcmd/cmds/AddBuildingCmd.java new file mode 100644 index 00000000..537faab1 --- /dev/null +++ b/src/engine/devcmd/cmds/AddBuildingCmd.java @@ -0,0 +1,127 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.ProtectionState; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import engine.server.MBServerStatics; + +public class AddBuildingCmd extends AbstractDevCmd { + + public AddBuildingCmd() { + super("addbuilding"); +// super("addbuilding", MBServerStatics.ACCESS_GROUP_DESIGNER_UP, 0, false, true); + this.addCmdString("building"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + // Arg Count Check + if (words.length != 2) { + this.sendUsage(pc); + return; + } + + int ID; + int rank; + Blueprint blueprint; + + try { + ID = Integer.parseInt(words[0]); + rank = Integer.parseInt(words[1]); + } catch (Exception e) { + throwbackError(pc, "Invalid addBuilding Command. Need Building ID and rank."); + return; // NaN + } + if (ID < 1) { + throwbackError(pc, + "Invalid addBuilding Command. Invalid Building ID."); + return; + } + Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f); + float w = 1f; + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (zone == null) { + throwbackError(pc, "Failed to find zone to place building in."); + return; + } + + blueprint = Blueprint.getBlueprint(ID); + + if ((blueprint != null) && (rank > blueprint.getMaxRank())) { + throwbackError(pc, rank + " is not a valid rank for this building"); + return; + } + + Building likeBuilding = DbManager.BuildingQueries.GET_BUILDING_BY_MESH(ID); + + if (likeBuilding != null) { + rot = likeBuilding.getRot(); + w = likeBuilding.getw(); + } + + String buildingName = ""; + int blueprintUUID = 0; + + Vector3fImmutable localLoc = ZoneManager.worldToLocal(pc.getLoc(), zone); + + if (localLoc == null) + return; + + if (blueprint != null) { + buildingName = blueprint.getName(); + blueprintUUID = blueprint.getBlueprintUUID(); + } + + Building building = DbManager.BuildingQueries. + CREATE_BUILDING( + zone.getObjectUUID(), 0, buildingName, ID, + localLoc, 1.0f, 0, ProtectionState.PROTECTED, 0, rank, + null, blueprintUUID, w, rot.y); + + + if (building == null) { + throwbackError(pc, "Failed to add building."); + return; + } + + building.setRot(rot); + building.setw(w); + + building.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + WorldGrid.addObject(building, pc); + ChatManager.chatSayInfo(pc, + "Building with ID " + building.getObjectUUID() + " added"); + + this.setResult(String.valueOf(building.getObjectUUID())); + + } + + @Override + protected String _getHelpString() { + return "Creates a building of type 'buildingID' at the location your character is standing."; + } + + @Override + protected String _getUsageString() { + return "' /addbuilding buildingID rank' || ' /building buildingID rank'"; + } + +} diff --git a/src/engine/devcmd/cmds/AddGoldCmd.java b/src/engine/devcmd/cmds/AddGoldCmd.java new file mode 100644 index 00000000..e998fc5b --- /dev/null +++ b/src/engine/devcmd/cmds/AddGoldCmd.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.Item; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class AddGoldCmd extends AbstractDevCmd { + + public AddGoldCmd() { + super("addgold"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + Item gold = pc.getCharItemManager().getGoldInventory(); + int curAmt; + if (gold == null) + curAmt = 0; + else + curAmt = gold.getNumOfItems(); + + int amt; + try { + amt = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + throwbackError(pc, "Quantity must be a number, " + words[0] + " is invalid"); + return; + } + if (amt < 1 || amt > 10000000) { + throwbackError(pc, "Quantity must be between 1 and 10000000 (10 million)"); + return; + } else if ((curAmt + amt) > 10000000) { + throwbackError(pc, "This would place your inventory over 10,000,000 gold."); + return; + } + + if (!pc.getCharItemManager().addGoldToInventory(amt, true)) { + throwbackError(pc, "Failed to add gold to inventory"); + return; + } + + ChatManager.chatSayInfo(pc, amt + " gold added to inventory"); + pc.getCharItemManager().updateInventory(); + } + + @Override + protected String _getHelpString() { + return "adds gold to inventory"; + } + + @Override + protected String _getUsageString() { + return "' /addGold quantity'"; + } + +} diff --git a/src/engine/devcmd/cmds/AddMobCmd.java b/src/engine/devcmd/cmds/AddMobCmd.java new file mode 100644 index 00000000..ac2fbfa8 --- /dev/null +++ b/src/engine/devcmd/cmds/AddMobCmd.java @@ -0,0 +1,110 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +/** + * @author Eighty + */ +public class AddMobCmd extends AbstractDevCmd { + + public AddMobCmd() { + super("mob"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (words[0].equals("all")) { + + for (AbstractGameObject mobbaseAGO : DbManager.getList(GameObjectType.MobBase)) { + MobBase mb = (MobBase) mobbaseAGO; + int loadID = mb.getObjectUUID(); + Mob mob = Mob.createMob(loadID, Vector3fImmutable.getRandomPointInCircle(pc.getLoc(), 100), + null, true, zone, null, 0, "", 1); + if (mob != null) { + mob.updateDatabase(); + this.setResult(String.valueOf(mob.getDBID())); + } else { + throwbackError(pc, "Failed to create mob of type " + loadID); + Logger.error("Failed to create mob of type " + + loadID); + } + } + return; + } + + + int loadID; + try { + loadID = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + throwbackError(pc, "Supplied type " + words[0] + + " failed to parse to an Integer"); + return; + } catch (Exception e) { + throwbackError(pc, + "An unknown exception occurred when trying to use mob command for type " + + words[0]); + return; // NaN + } + + + if (zone == null) { + throwbackError(pc, "Failed to find zone to place mob in."); + return; + } + + if (zone.isPlayerCity()) { + throwbackError(pc, "Cannot use ./mob on Player cities. Try ./servermob instead."); + return; + } + + + Mob mob = Mob.createMob(loadID, pc.getLoc(), + null, true, zone, null, 0, "", 1); + if (mob != null) { + mob.updateDatabase(); + ChatManager.chatSayInfo(pc, + "Mob with ID " + mob.getDBID() + " added"); + this.setResult(String.valueOf(mob.getDBID())); + } else { + throwbackError(pc, "Failed to create mob of type " + loadID); + Logger.error("Failed to create mob of type " + + loadID); + } + } + + @Override + protected String _getHelpString() { + return "Creates a Mob of type 'mobID' at the location your character is standing"; + } + + @Override + protected String _getUsageString() { + return "' /mob mobID'"; + } + +} diff --git a/src/engine/devcmd/cmds/AddNPCCmd.java b/src/engine/devcmd/cmds/AddNPCCmd.java new file mode 100644 index 00000000..495d2517 --- /dev/null +++ b/src/engine/devcmd/cmds/AddNPCCmd.java @@ -0,0 +1,107 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.*; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +/** + * @author Eighty + */ +public class AddNPCCmd extends AbstractDevCmd { + + public AddNPCCmd() { + super("npc"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + int contractID; + String name = ""; + int level = 0; + + if (words.length < 2) { + this.sendUsage(pc); + return; + } + try { + contractID = Integer.parseInt(words[0]); + level = Integer.parseInt(words[1]); + + for (int i = 2; i < words.length; i++) { + name += words[i]; + if (i + 1 < words.length) + name += ""; + } + + } catch (NumberFormatException e) { + throwbackError(pc, + "Failed to parse supplied contractID or level to an Integer."); + return; // NaN + } + + Contract contract = DbManager.ContractQueries.GET_CONTRACT(contractID); + + if (contract == null || level < 1 || level > 75) { + throwbackError(pc, + "Invalid addNPC Command. Need contract ID, and level"); + return; // NaN + } + + // Pick a random name + if (name.isEmpty()) + name = NPCManager.getPirateName(contract.getMobbaseID()); + + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (zone == null) { + throwbackError(pc, "Failed to find zone to place npc in."); + return; + } + + if (target != null) + if (target.getObjectType() == GameObjectType.Building) { + Building parentBuilding = (Building) target; + BuildingManager.addHirelingForWorld(parentBuilding, pc, parentBuilding.getLoc(), parentBuilding.getParentZone(), contract, level); + return; + } + + NPC npc = NPC.createNPC(name, contractID, + pc.getLoc(), null, zone, (short) level, null); + + if (npc != null) { + WorldGrid.addObject(npc, pc); + ChatManager.chatSayInfo(pc, + "NPC with ID " + npc.getDBID() + " added"); + this.setResult(String.valueOf(npc.getDBID())); + } else { + throwbackError(pc, "Failed to create npc of contract type " + + contractID); + Logger.error( + "Failed to create npc of contract type " + contractID); + } + } + + @Override + protected String _getHelpString() { + return "Creates an NPC of type 'npcID' at the location your character is standing"; + } + + @Override + protected String _getUsageString() { + return "' /npc npcID level name'"; + } + +} diff --git a/src/engine/devcmd/cmds/ApplyBonusCmd.java b/src/engine/devcmd/cmds/ApplyBonusCmd.java new file mode 100644 index 00000000..898332fc --- /dev/null +++ b/src/engine/devcmd/cmds/ApplyBonusCmd.java @@ -0,0 +1,156 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.PowerActionType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.PowersManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; +import engine.powers.effectmodifiers.AbstractEffectModifier; +import engine.util.ThreadUtils; +import org.pmw.tinylog.Logger; + +import java.util.HashMap; +import java.util.HashSet; + +public class ApplyBonusCmd extends AbstractDevCmd { + + public ApplyBonusCmd() { + super("applybonus"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + + String action = words[0]; + + PowerActionType actionType = null; + + HashMap> appliedMods = new HashMap<>(); + + try { + + if (action.equals("all") == false) + for (PowerActionType powerActionType : PowerActionType.values()) { + if (powerActionType.name().equalsIgnoreCase(action) == false) + continue; + actionType = powerActionType; + break; + } + + } catch (Exception e) { + this.throwbackError(pcSender, "Invalid power Action type for " + action); + this.throwbackInfo(pcSender, "Valid Types : " + this.getActionTypes()); + return; + } + if (action.equals("all") == false) + if (actionType == null) { + this.throwbackError(pcSender, "Invalid power Action type for " + action); + this.throwbackInfo(pcSender, "Valid Types : " + this.getActionTypes()); + return; + } + + + for (PowersBase pb : PowersManager.powersBaseByIDString.values()) { + if (pb.getActions() == null || pb.getActions().isEmpty()) + continue; + + for (ActionsBase ab : pb.getActions()) { + if (ab.getPowerAction() == null) + continue; + if (action.equals("all") == false) + if (ab.getPowerAction().getType().equalsIgnoreCase(action) == false) + continue; + String effect1 = ""; + String effect2 = ""; + ChatManager.chatSystemInfo(pcSender, "Applying Power " + pb.getName() + " : " + pb.getDescription()); + if (ab.getPowerAction().getEffectsBase() == null) { + + try { + PowersManager.runPowerAction(pcSender, pcSender, pcSender.getLoc(), ab, 1, pb); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + ThreadUtils.sleep(500); + continue; + } + + + if (ab.getPowerAction().getEffectsBase().getModifiers() == null || ab.getPowerAction().getEffectsBase().getModifiers().isEmpty()) { + try { + PowersManager.runPowerAction(pcSender, pcSender, pcSender.getLoc(), ab, 1, pb); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + continue; + } + + boolean run = true; + for (AbstractEffectModifier mod : ab.getPowerAction().getEffectsBase().getModifiers()) { + if (appliedMods.containsKey(mod.modType.name()) == false) { + appliedMods.put(mod.modType.name(), new HashSet<>()); + } + +// if (appliedMods.get(mod.modType.name()).contains(mod.sourceType.name())){ +// continue; +// } + + appliedMods.get(mod.modType.name()).add(mod.sourceType.name()); + try { + try { + PowersManager.runPowerAction(pcSender, pcSender, pcSender.getLoc(), ab, 1, pb); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } catch (Exception e) { + Logger.error(e); + } + break; + + + } + + } + } + } + + + @Override + protected String _getUsageString() { + return "' /bounds'"; + } + + @Override + protected String _getHelpString() { + return "Audits all the mobs in a zone."; + + } + + private String getActionTypes() { + String output = ""; + + for (PowerActionType actionType : PowerActionType.values()) { + output += actionType.name() + " | "; + + } + + return output.substring(0, output.length() - 3); + } + +} diff --git a/src/engine/devcmd/cmds/ApplyStatModCmd.java b/src/engine/devcmd/cmds/ApplyStatModCmd.java new file mode 100644 index 00000000..c2797ce6 --- /dev/null +++ b/src/engine/devcmd/cmds/ApplyStatModCmd.java @@ -0,0 +1,144 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.PowersManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.powers.PowersBase; + +import java.util.ArrayList; + +/** + * @author Eighty + */ +public class ApplyStatModCmd extends AbstractDevCmd { + + private static int cnt = 0; + + public ApplyStatModCmd() { + super("applystatmod"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (args.length < 1) { + // if(args.length < 2) { + this.sendUsage(pcSender); + return; + } + + if (!(target instanceof AbstractCharacter)) { + this.sendHelp(pcSender); + return; + } + + this.setTarget(pcSender); //for logging + + int spellID; + int powerAction = 0; + if (args[0].toLowerCase().contains("all")) { + + int amount = 0; + if (args.length == 1) { + amount = ApplyStatModCmd.cnt; + ApplyStatModCmd.cnt++; + } else { + amount = Integer.valueOf(args[1]); + ApplyStatModCmd.cnt = amount + 1; + } + + + ArrayList pbList = new ArrayList<>(); + pbList.add(PowersManager.getPowerByToken(429047968)); + pbList.add(PowersManager.getPowerByToken(429768864)); + pbList.add(PowersManager.getPowerByToken(428458144)); + pbList.add(PowersManager.getPowerByToken(428677994)); + pbList.add(PowersManager.getPowerByToken(431874079)); + pbList.add(PowersManager.getPowerByToken(431081336)); + + + for (PowersBase pb : pbList) { + if (amount <= 0) { + if (pb.getToken() == 428677994) + powerAction = 1; + PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false); + continue; + } else if (amount > 9999 || amount < 21) { + ChatManager.chatSystemInfo(pcSender, "Amount must be between 21 and 9999 inclusive."); + return; + } + + if (pb.getToken() == 428677994) { + PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false); + PowersManager.runPowerAction(pcSender, pcSender, Vector3fImmutable.ZERO, pb.getActions().get(powerAction), amount - 20, pb); + } + if (pb.getToken() == 428677994) + powerAction = 1; + PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false); + PowersManager.runPowerAction(pcSender, pcSender, Vector3fImmutable.ZERO, pb.getActions().get(powerAction), amount - 20, pb); + } + return; + } + if (args[0].toLowerCase().contains("con")) { + spellID = 429047968; //Blessing of Health + } else if (args[0].toLowerCase().contains("str")) { + spellID = 429768864; //Blessing of Might + } else if (args[0].toLowerCase().contains("dex")) { + spellID = 428458144; //Blessing of Dexterity + } else if (args[0].toLowerCase().contains("int")) { + spellID = 428677994; //Bard Spi - TODO + powerAction = 1; + } else if (args[0].toLowerCase().contains("spi")) { + spellID = 428677994; //Bard Spi + } else { + ChatManager.chatSystemInfo(pcSender, "No valid stat found."); + return; + } + + PowersBase pb = PowersManager.getPowerByToken(spellID); + + int amount = 0; + if (args.length == 1) { + amount = ApplyStatModCmd.cnt; + ApplyStatModCmd.cnt++; + } else { + amount = Integer.valueOf(args[1]); + ApplyStatModCmd.cnt = amount + 1; + } + // int amount = Integer.valueOf(args[1]); + if (amount <= 0) { + PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false); + return; + } else if (amount > 9999 || amount < 21) { + ChatManager.chatSystemInfo(pcSender, "Amount must be between 21 and 9999 inclusive."); + return; + } + + PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false); + PowersManager.runPowerAction(pcSender, pcSender, Vector3fImmutable.ZERO, pb.getActions().get(powerAction), amount - 20, pb); + } + + @Override + protected String _getUsageString() { + return "' /applystatmod [trains]'"; + } + + @Override + protected String _getHelpString() { + return "You must be targeting a player!"; + } + +} diff --git a/src/engine/devcmd/cmds/AuditFailedItemsCmd.java b/src/engine/devcmd/cmds/AuditFailedItemsCmd.java new file mode 100644 index 00000000..e92e0b87 --- /dev/null +++ b/src/engine/devcmd/cmds/AuditFailedItemsCmd.java @@ -0,0 +1,127 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.ModType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.PowersManager; +import engine.net.ItemProductionManager; +import engine.objects.*; +import engine.powers.effectmodifiers.AbstractEffectModifier; +import engine.powers.poweractions.AbstractPowerAction; +import org.pmw.tinylog.Logger; + +public class AuditFailedItemsCmd extends AbstractDevCmd { + + public AuditFailedItemsCmd() { + super("faileditems"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + + + if (ItemProductionManager.FailedItems.isEmpty()) + return; + + Logger.info("Auditing Item production Failed Items"); + + String newLine = "\r\n"; + String auditFailedItem = "Failed Item Name | Prefix | Suffix | NPC | Contract | Player | "; + + for (ProducedItem failedItem : ItemProductionManager.FailedItems) { + + String npcName = ""; + String playerName = ""; + String contractName = ""; + + String prefix = ""; + String suffix = ""; + String itemName = ""; + NPC npc = NPC.getFromCache(failedItem.getNpcUID()); + + if (npc == null) { + npcName = "null"; + contractName = "null"; + } else { + npcName = npc.getName(); + if (npc.getContract() != null) + contractName = npc.getContract().getName(); + } + + PlayerCharacter roller = PlayerCharacter.getFromCache(failedItem.getPlayerID()); + + if (roller == null) + playerName = "null"; + else + playerName = roller.getName(); + + ItemBase ib = ItemBase.getItemBase(failedItem.getItemBaseID()); + + if (ib != null) { + itemName = ib.getName(); + } + + if (failedItem.isRandom() == false) { + if (failedItem.getPrefix().isEmpty() == false) { + AbstractPowerAction pa = PowersManager.getPowerActionByIDString(failedItem.getPrefix()); + if (pa != null) { + for (AbstractEffectModifier aem : pa.getEffectsBase().getModifiers()) { + if (aem.modType.equals(ModType.ItemName)) { + prefix = aem.getString1(); + break; + } + } + } + + } + + if (failedItem.getSuffix().isEmpty() == false) { + AbstractPowerAction pa = PowersManager.getPowerActionByIDString(failedItem.getSuffix()); + if (pa != null) { + for (AbstractEffectModifier aem : pa.getEffectsBase().getModifiers()) { + if (aem.modType.equals(ModType.ItemName)) { + suffix = aem.getString1(); + break; + } + } + } + + } + + } else { + prefix = "random"; + } + + + auditFailedItem += newLine; + auditFailedItem += itemName + " | " + prefix + " | " + suffix + " | " + failedItem.getNpcUID() + ":" + npcName + " | " + contractName + " | " + failedItem.getPlayerID() + ":" + playerName; + + } + Logger.info(auditFailedItem); + ItemProductionManager.FailedItems.clear(); + + + } + + + @Override + protected String _getUsageString() { + return "' /bounds'"; + } + + @Override + protected String _getHelpString() { + return "Audits all the mobs in a zone."; + + } + +} diff --git a/src/engine/devcmd/cmds/AuditHeightMapCmd.java b/src/engine/devcmd/cmds/AuditHeightMapCmd.java new file mode 100644 index 00000000..66e6d1ed --- /dev/null +++ b/src/engine/devcmd/cmds/AuditHeightMapCmd.java @@ -0,0 +1,70 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.HeightMap; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.math.Vector2f; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + +public class AuditHeightMapCmd extends AbstractDevCmd { + + public AuditHeightMapCmd() { + super("auditheightmap"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + + int count = Integer.parseInt(words[0]); + long start = System.currentTimeMillis(); + for (int i = 0; i < count; i++) { + + + Zone currentZone = ZoneManager.findSmallestZone(pcSender.getLoc()); + + + Vector3fImmutable currentLoc = Vector3fImmutable.getRandomPointInCircle(currentZone.getLoc(), currentZone.getBounds().getHalfExtents().x < currentZone.getBounds().getHalfExtents().y ? currentZone.getBounds().getHalfExtents().x : currentZone.getBounds().getHalfExtents().y); + + Vector2f zoneLoc = ZoneManager.worldToZoneSpace(currentLoc, currentZone); + + if (currentZone != null && currentZone.getHeightMap() != null) { + float altitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc); + float outsetAltitude = HeightMap.getOutsetHeight(altitude, currentZone, pcSender.getLoc()); + } + + } + long end = System.currentTimeMillis(); + + long delta = end - start; + + this.throwbackInfo(pcSender, "Audit Heightmap took " + delta + " ms to run " + count + " times!"); + + + } + + + @Override + protected String _getUsageString() { + return "' /auditmobs [zone.UUID]'"; + } + + @Override + protected String _getHelpString() { + return "Audits all the mobs in a zone."; + + } + +} diff --git a/src/engine/devcmd/cmds/AuditMobsCmd.java b/src/engine/devcmd/cmds/AuditMobsCmd.java new file mode 100644 index 00000000..d8586911 --- /dev/null +++ b/src/engine/devcmd/cmds/AuditMobsCmd.java @@ -0,0 +1,105 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + +public class AuditMobsCmd extends AbstractDevCmd { + + public AuditMobsCmd() { + super("auditmobs"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + if (pcSender == null) + return; + + //get Zone to check mobs against + + Zone zone; + + if (words.length == 2) { + if (words[0].equals("all")) { + int plusplus = 0; + int count = Integer.parseInt(words[1]); + for (Zone zoneMicro : ZoneManager.getAllZones()) { + int size = zoneMicro.zoneMobSet.size(); + + if (size >= count) { + plusplus++; + throwbackInfo(pcSender, zoneMicro.getName() + " at location " + zoneMicro.getLoc().toString() + " has " + size + " mobs. "); + System.out.println(zoneMicro.getName() + " at location " + zoneMicro.getLoc().toString() + " has " + size + " mobs. "); + } + + + } + throwbackInfo(pcSender, " there are " + plusplus + " zones with at least " + count + " mobs in each."); + } + return; + } + if (words.length > 1) { + this.sendUsage(pcSender); + return; + } else if (words.length == 1) { + int uuid; + try { + uuid = Integer.parseInt(words[0]); + zone = ZoneManager.getZoneByUUID(uuid); + } catch (NumberFormatException e) { + zone = ZoneManager.findSmallestZone(pcSender.getLoc()); + } + } else + zone = ZoneManager.findSmallestZone(pcSender.getLoc()); + + if (zone == null) { + throwbackError(pcSender, "Unable to find the zone"); + return; + } + + //get list of mobs for zone + + if (zone.zoneMobSet.isEmpty()) { + throwbackError(pcSender, "No mobs found for this zone."); + return; + } + + // ConcurrentHashMap spawnMap = Mob.getSpawnMap(); + //ConcurrentHashMap respawnMap = Mob.getRespawnMap(); + // ConcurrentHashMap despawnMap = Mob.getDespawnMap(); + + throwbackInfo(pcSender, zone.getName() + ", numMobs: " + zone.zoneMobSet.size()); + throwbackInfo(pcSender, "UUID, dbID, inRespawnMap, isAlive, activeAI, Loc"); + + + //mob not found in spawn map, check respawn + boolean inRespawn = false; + + + } + + + @Override + protected String _getUsageString() { + return "' /auditmobs [zone.UUID]'"; + } + + @Override + protected String _getHelpString() { + return "Audits all the mobs in a zone."; + + } + +} diff --git a/src/engine/devcmd/cmds/BoundsCmd.java b/src/engine/devcmd/cmds/BoundsCmd.java new file mode 100644 index 00000000..e6a1a7aa --- /dev/null +++ b/src/engine/devcmd/cmds/BoundsCmd.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +public class BoundsCmd extends AbstractDevCmd { + + public BoundsCmd() { + super("bounds"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + if (target == null || !target.getObjectType().equals(GameObjectType.Building)) { + this.throwbackError(pcSender, "No Building Selected"); + return; + } + + Building building = (Building) target; + + if (building.getBounds() == null) { + this.throwbackInfo(pcSender, "No valid Bounds for building UUID " + building.getObjectUUID()); + return; + } + float topLeftX = building.getLoc().x - building.getBounds().getHalfExtents().x; + float topLeftY = building.getLoc().z - building.getBounds().getHalfExtents().y; + + float topRightX = building.getLoc().x + building.getBounds().getHalfExtents().x; + float topRightY = building.getLoc().z - building.getBounds().getHalfExtents().y; + + float bottomLeftX = building.getLoc().x - building.getBounds().getHalfExtents().x; + float bottomLeftY = building.getLoc().z + building.getBounds().getHalfExtents().y; + + float bottomRightX = building.getLoc().x + building.getBounds().getHalfExtents().x; + float bottomRightY = building.getLoc().z + building.getBounds().getHalfExtents().y; + + String newLine = "\r\n "; + + String output = "Bounds Information for Building UUID " + building.getObjectUUID(); + output += newLine; + + output += "Top Left : " + topLeftX + " , " + topLeftY + newLine; + output += "Top Right : " + topRightX + " , " + topRightY + newLine; + output += "Bottom Left : " + bottomLeftX + " , " + bottomLeftY + newLine; + output += "Bottom Right : " + bottomRightX + " , " + bottomRightY + newLine; + + this.throwbackInfo(pcSender, output); + + + } + + + @Override + protected String _getUsageString() { + return "' /bounds'"; + } + + @Override + protected String _getHelpString() { + return "Audits all the mobs in a zone."; + + } + +} diff --git a/src/engine/devcmd/cmds/ChangeNameCmd.java b/src/engine/devcmd/cmds/ChangeNameCmd.java new file mode 100644 index 00000000..e8676bdd --- /dev/null +++ b/src/engine/devcmd/cmds/ChangeNameCmd.java @@ -0,0 +1,114 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.util.MiscUtils; + +public class ChangeNameCmd extends AbstractDevCmd { + + public ChangeNameCmd() { + super("changename"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + Vector3fImmutable loc = null; + + // Arg Count Check + if (words.length < 2) { + this.sendUsage(pc); + return; + } + + String oldFirst = words[0]; + String newFirst = words[1]; + String newLast = ""; + if (words.length > 2) { + newLast = words[2]; + for (int i = 3; i < words.length; i++) + newLast += ' ' + words[i]; + } + + //verify new name length + if (newFirst.length() < 3) { + this.throwbackError(pc, "Error: First name is incorrect length. Must be between 3 and 15 characters"); + return; + } + + //verify old name length + if (newLast.length() > 50) { + this.throwbackError(pc, "Error: Last name is incorrect length. Must be no more than 50 characters"); + return; + } + + // Check if firstname is valid + if (MiscUtils.checkIfFirstNameInvalid(newFirst)) { + this.throwbackError(pc, "Error: First name is not allowed"); + return; + } + + + //get the world ID we're modifying for + + //test if first name is unique, unless new and old first name are equal. + if (!(oldFirst.equals(newFirst))) { + if (!DbManager.PlayerCharacterQueries.IS_CHARACTER_NAME_UNIQUE(newFirst)) { + this.throwbackError(pc, "Error: First name is not unique."); + return; + } + } + + //tests passed, update name in database + if (!DbManager.PlayerCharacterQueries.UPDATE_NAME(oldFirst, newFirst, newLast)) { + this.throwbackError(pc, "Error: Database failed to update the name."); + return; + } + + //Finally update player ingame + PlayerCharacter pcTar = null; + try { + pcTar = SessionManager + .getPlayerCharacterByLowerCaseName(words[0]); + pcTar.setFirstName(newFirst); + pcTar.setLastName(newLast); + this.setTarget(pcTar); //for logging + + //specify if last name is ascii characters only + String lastAscii = newLast.replaceAll("[^\\p{ASCII}]", ""); + pcTar.setAsciiLastName(lastAscii.equals(newLast)); + } catch (Exception e) { + this.throwbackError(pc, "Database was updated but ingame character failed to update."); + return; + } + + String out = oldFirst + " was changed to " + newFirst + (newLast.isEmpty() ? "." : (' ' + newLast + '.')); + this.throwbackInfo(pc, out); + + + } + + @Override + protected String _getHelpString() { + return "Changes the name of a player"; + } + + @Override + protected String _getUsageString() { + return "'./changename oldFirstName newFirstName newLastName'"; + } + +} diff --git a/src/engine/devcmd/cmds/CombatMessageCmd.java b/src/engine/devcmd/cmds/CombatMessageCmd.java new file mode 100644 index 00000000..6f1254d5 --- /dev/null +++ b/src/engine/devcmd/cmds/CombatMessageCmd.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.net.client.msg.TargetedActionMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class CombatMessageCmd extends AbstractDevCmd { + + public CombatMessageCmd() { + super("cm"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (pcSender == null) + return; + if (args.length != 1) { + this.sendUsage(pcSender); + return; + } + int num = 0; + try { + num = Integer.parseInt(args[0]); + } catch (NumberFormatException e) { + throwbackError(pcSender, "Supplied message number " + args[0] + " failed to parse to an Integer"); + return; + } + TargetedActionMsg.un2cnt = num; + throwbackInfo(pcSender, "CombatMessage set to " + num); + } + + @Override + protected String _getUsageString() { + return "' /cm [cmNumber]'"; + } + + @Override + protected String _getHelpString() { + return "Sets the combat message to the supplied integer value"; + } + +} diff --git a/src/engine/devcmd/cmds/CreateItemCmd.java b/src/engine/devcmd/cmds/CreateItemCmd.java new file mode 100644 index 00000000..813f2b41 --- /dev/null +++ b/src/engine/devcmd/cmds/CreateItemCmd.java @@ -0,0 +1,64 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.ItemBase; +import engine.objects.ItemFactory; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class CreateItemCmd extends AbstractDevCmd { + + public CreateItemCmd() { + super("createitem"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (words.length < 2) { + this.sendUsage(pc); + return; + } + int id; + id = ItemBase.getIDByName(words[0]); + + if (id == 0) + id = Integer.parseInt(words[0]); + if (id == 7) { + this.throwbackInfo(pc, "use /addgold to add gold....."); + return; + } + + int size = 1; + + if (words.length < 3) { + size = Integer.parseInt(words[1]); + } + + ItemFactory.fillInventory(pc, id, size); + + } + + @Override + protected String _getHelpString() { + return "Fill your inventory with items"; + } + + @Override + protected String _getUsageString() { + return "' /createitem '"; + } + +} diff --git a/src/engine/devcmd/cmds/DebugCmd.java b/src/engine/devcmd/cmds/DebugCmd.java new file mode 100644 index 00000000..ef1111c2 --- /dev/null +++ b/src/engine/devcmd/cmds/DebugCmd.java @@ -0,0 +1,122 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.jobs.DebugTimerJob; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class DebugCmd extends AbstractDevCmd { + + + public DebugCmd() { + super("debug"); + // super("debug", MBServerStatics.ACCESS_GROUP_ALL_TEAM, 0, false, false); + } + + private static void toggleDebugTimer(PlayerCharacter pc, String name, int num, int duration, boolean toggle) { + if (toggle) { + DebugTimerJob dtj = new DebugTimerJob(pc, name, num, duration); + pc.renewTimer(name, dtj, duration); + } else + pc.cancelTimer(name); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (words.length < 2) { + this.sendUsage(pc); + return; + } + + if (pc == null) + return; + + //pc.setDebug must use bit sizes: 1, 2, 4, 8, 16, 32 + + String command = words[0].toLowerCase(); + boolean toggle = (words[1].toLowerCase().equals("on")) ? true : false; + + switch (command) { + case "magictrek": + pc.RUN_MAGICTREK = toggle; + + break; + case "combat": + pc.setDebug(64, toggle); + + break; + case "health": + toggleDebugTimer(pc, "Debug_Health", 1, 1000, toggle); + + break; + case "mana": + toggleDebugTimer(pc, "Debug_Mana", 2, 1000, toggle); + + break; + case "stamina": + toggleDebugTimer(pc, "Debug_Stamina", 3, 500, toggle); + + break; + case "spells": + pc.setDebug(16, toggle); + + break; + case "damageabsorber": + pc.setDebug(2, toggle); + + break; + case "recast": + case "recycle": + pc.setDebug(4, toggle); + + break; + case "seeinvis": + pc.setDebug(8, toggle); + + break; + case "movement": + pc.setDebug(1, toggle); + + break; + case "noaggro": + pc.setDebug(32, toggle); + + break; + // case "loot": + // MBServerStatics.debugLoot = toggle; + // break; + + default: + String output = "Debug for " + command + " not found."; + throwbackError(pc, output); + return; + } + + + setTarget(pc); //for logging + + String output = "Debug for " + command + " turned " + ((toggle) ? "on." : "off."); + throwbackInfo(pc, output); + } + + @Override + protected String _getHelpString() { + return "Runs debug commands"; + + } + + @Override + protected String _getUsageString() { + return "'./Debug command on/off'"; + } +} diff --git a/src/engine/devcmd/cmds/DebugMeleeSyncCmd.java b/src/engine/devcmd/cmds/DebugMeleeSyncCmd.java new file mode 100644 index 00000000..ff97a168 --- /dev/null +++ b/src/engine/devcmd/cmds/DebugMeleeSyncCmd.java @@ -0,0 +1,57 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class DebugMeleeSyncCmd extends AbstractDevCmd { + + public DebugMeleeSyncCmd() { + super("debugmeleesync"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + String s = words[0].toLowerCase(); + + if (s.equals("on")) { + pc.setDebug(64, true); + ChatManager.chatSayInfo(pc, "Melee Sync Debug ON"); + } else if (s.equals("off")) { + pc.setDebug(64, false); + ChatManager.chatSayInfo(pc, "Melee Sync Debug OFF"); + } else { + this.sendUsage(pc); + } + } + + @Override + protected String _getHelpString() { + return "Turns on/off melee sync debugging."; + + } + + @Override + protected String _getUsageString() { + return "'./debugmeleesync on|off'"; + + } + +} diff --git a/src/engine/devcmd/cmds/DecachePlayerCmd.java b/src/engine/devcmd/cmds/DecachePlayerCmd.java new file mode 100644 index 00000000..d4e006e2 --- /dev/null +++ b/src/engine/devcmd/cmds/DecachePlayerCmd.java @@ -0,0 +1,54 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +/** + * + */ +public class DecachePlayerCmd extends AbstractDevCmd { + + public DecachePlayerCmd() { + super("decacheplayer"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (words.length < 1) { + this.sendUsage(pc); + } + + int objectUUID = Integer.parseInt(words[0]); + + if (DbManager.inCache(Enum.GameObjectType.PlayerCharacter, objectUUID)) { + this.setTarget(PlayerCharacter.getFromCache(objectUUID)); //for logging + PlayerCharacter.getFromCache(objectUUID).removeFromCache(); + } else { + this.sendHelp(pc); + } + } + + @Override + protected String _getHelpString() { + return "No player found. Please make sure the table ID is correct."; + } + + @Override + protected String _getUsageString() { + return "' /decacheplayer '"; + } + +} diff --git a/src/engine/devcmd/cmds/DespawnCmd.java b/src/engine/devcmd/cmds/DespawnCmd.java new file mode 100644 index 00000000..02f7ce98 --- /dev/null +++ b/src/engine/devcmd/cmds/DespawnCmd.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; + +public class DespawnCmd extends AbstractDevCmd { + + public DespawnCmd() { + super("debugmob"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + + if (pc == null) { + return; + } + + + Mob mob = null; + + if (target != null && target.getObjectType().equals(GameObjectType.Mob)) + mob = (Mob) target; + + if (mob == null) + mob = Mob.getFromCache(Integer.parseInt(words[1])); + + if (mob == null) + return; + + if (words[0].equalsIgnoreCase("respawn")) { + mob.respawn(); + this.throwbackInfo(pc, "Mob with ID " + mob.getObjectUUID() + " Respawned"); + } else if (words[0].equalsIgnoreCase("despawn")) { + mob.despawn(); + this.throwbackInfo(pc, "Mob with ID " + mob.getObjectUUID() + " Despawned"); + } + } + + @Override + protected String _getHelpString() { + return "Gets distance from a target."; + + } + + @Override + protected String _getUsageString() { + return "' /distance'"; + + } + +} diff --git a/src/engine/devcmd/cmds/DistanceCmd.java b/src/engine/devcmd/cmds/DistanceCmd.java new file mode 100644 index 00000000..81e02c3f --- /dev/null +++ b/src/engine/devcmd/cmds/DistanceCmd.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerCharacter; + +public class DistanceCmd extends AbstractDevCmd { + + public DistanceCmd() { + super("distance"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + if (pc == null) { + return; + } + + if (target == null || !(target instanceof AbstractWorldObject)) { + throwbackError(pc, "No target found."); + return; + } + + AbstractWorldObject awoTarget = (AbstractWorldObject) target; + + Vector3fImmutable pcLoc = pc.getLoc(); + Vector3fImmutable tarLoc = awoTarget.getLoc(); + String out = "Distance: " + pcLoc.distance(tarLoc) + + "\r\nYour Loc: " + pcLoc.x + 'x' + pcLoc.y + 'x' + pcLoc.z + + "\r\nTarget Loc: " + tarLoc.x + 'x' + tarLoc.y + 'x' + tarLoc.z; + throwbackInfo(pc, out); + } + + @Override + protected String _getHelpString() { + return "Gets distance from a target."; + + } + + @Override + protected String _getUsageString() { + return "' /distance'"; + + } + +} diff --git a/src/engine/devcmd/cmds/EffectCmd.java b/src/engine/devcmd/cmds/EffectCmd.java new file mode 100644 index 00000000..8ad9ca8d --- /dev/null +++ b/src/engine/devcmd/cmds/EffectCmd.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.PowersManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.powers.EffectsBase; + +/** + * @author Eighty + */ +public class EffectCmd extends AbstractDevCmd { + + public EffectCmd() { + super("effect"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + int ID = 0; + int token = 0; + + if (args.length != 2) { + this.sendUsage(pcSender); + return; + } + ID = Integer.parseInt(args[0]); + token = Integer.parseInt(args[1]); + + EffectsBase eb = PowersManager.setEffectToken(ID, token); + if (eb == null) { + throwbackError(pcSender, "Unable to find EffectsBase " + ID + + " to modify."); + return; + } + ChatManager.chatSayInfo(pcSender, + "EffectsBase with ID " + ID + " changed token to " + token); + } + + @Override + protected String _getUsageString() { + return "' /effect EffectsBaseID Token'"; + } + + @Override + protected String _getHelpString() { + return "Temporarily places the effect token with the corresponding EffectsBase on the server"; + } + +} diff --git a/src/engine/devcmd/cmds/EnchantCmd.java b/src/engine/devcmd/cmds/EnchantCmd.java new file mode 100644 index 00000000..1f23ae86 --- /dev/null +++ b/src/engine/devcmd/cmds/EnchantCmd.java @@ -0,0 +1,87 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +public class EnchantCmd extends AbstractDevCmd { + + public EnchantCmd() { + super("enchant"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + int rank = 0; + if (words.length < 1) { + this.sendUsage(pc); + return; + } + + + try { + rank = Integer.parseInt(words[0]); + } catch (Exception e) { + + } + + + Item item; + if (target == null || target instanceof Item) + item = (Item) target; + else { + throwbackError(pc, "Must have an item targeted"); + return; + } + + CharacterItemManager cim = pc.getCharItemManager(); + if (cim == null) { + throwbackError(pc, "Unable to find the character item manager for player " + pc.getFirstName() + '.'); + return; + } + + if (words[0].equals("clear")) { + item.clearEnchantments(); + cim.updateInventory(); + this.setResult(String.valueOf(item.getObjectUUID())); + } else { + int cnt = words.length; + for (int i = 1; i < cnt; i++) { + String enchant = words[i]; + boolean valid = true; + for (Effect eff : item.getEffects().values()) { + if (eff.getEffectsBase().getIDString().equals(enchant)) { + throwbackError(pc, "This item already has that enchantment"); + return; + } + } + if (valid) { + item.addPermanentEnchantmentForDev(enchant, rank); + this.setResult(String.valueOf(item.getObjectUUID())); + } else + throwbackError(pc, "Invalid Enchantment. Enchantment must consist of SUF-001 to SUF-328 or PRE-001 to PRE-334. Sent " + enchant + '.'); + } + cim.updateInventory(); + } + } + + @Override + protected String _getHelpString() { + return "Enchants an item with a prefix and suffix"; + } + + @Override + protected String _getUsageString() { + return "' /enchant clear/Enchant1 Enchant2 Enchant3 ...'"; + } + +} diff --git a/src/engine/devcmd/cmds/FindBuildingsCmd.java b/src/engine/devcmd/cmds/FindBuildingsCmd.java new file mode 100644 index 00000000..89f64424 --- /dev/null +++ b/src/engine/devcmd/cmds/FindBuildingsCmd.java @@ -0,0 +1,111 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + +import java.util.HashSet; + +public class FindBuildingsCmd extends AbstractDevCmd { + + public FindBuildingsCmd() { + super("findBuildings"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + try { + Vector3fImmutable searchPt = pc.getLoc(); + float range = 50.0f; + + // Arg Count Check + // Valid arg count is 0,1,2,3 + if (words.length == 0) { + // Use Player's loc and default range + + } else if (words.length == 1) { + // Use Player's loc and specified range + range = Float.valueOf(words[0]); + + } else if (words.length == 2) { + // Use specified loc and default range + searchPt = new Vector3fImmutable(Float.valueOf(words[0]), + searchPt.y, + Float.valueOf(words[1])); + + } else if (words.length == 3) { + // Use specified loc and specified range + searchPt = new Vector3fImmutable(Float.valueOf(words[0]), + searchPt.y, + Float.valueOf(words[1])); + range = Float.valueOf(words[2]); + + } else { + this.sendUsage(pc); + return; + } + + String s = ""; + + HashSet container = WorldGrid.getObjectsInRangePartial( + searchPt, range, MBServerStatics.MASK_BUILDING); + + s += "Found " + container.size(); + s += " buildings within " + range; + s += " units of [" + searchPt.toString() + ']'; + throwbackInfo(pc, s); + + int index = 0; + for (AbstractWorldObject awo : container) { + Building b = (Building) awo; + + s = index + ")"; + s += " ObjectID: " + awo.getObjectUUID() + ']'; + s += " -> Name: " + b.getSimpleName(); + if (b.getBlueprint() == null) { + s += " No Blueprint"; + } else { + s += " Blueprint UUID: " + b.getBlueprint().getMeshForRank(0); + } + s += "[" + ((Building) awo).getBlueprintUUID() + ']'; + + throwbackInfo(pc, s); + ++index; + } + + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied data: '" + words + + "' failed to parse to a Float."); + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to findBuildings with data: '" + + words + '\''); + } + } + + @Override + protected String _getHelpString() { + return "Sets your character's Mana to 'amount'"; + } + + @Override + protected String _getUsageString() { + return "' /findBuildings [lat long] [range]'"; + } + +} diff --git a/src/engine/devcmd/cmds/FlashMsgCmd.java b/src/engine/devcmd/cmds/FlashMsgCmd.java new file mode 100644 index 00000000..66565b62 --- /dev/null +++ b/src/engine/devcmd/cmds/FlashMsgCmd.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class FlashMsgCmd extends AbstractDevCmd { + + public FlashMsgCmd() { + super("flash"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (args.length != 1 || args[0].isEmpty()) { + this.sendUsage(pcSender); + return; + } + ChatManager.chatSystemFlash(args[0]); + } + + /** + * This function is called by the DevCmdManager. Override to avoid splitting + * argString into String array, since flash displays full String as message, + * then calls the subclass specific _doCmd method. + */ + + @Override + public void doCmd(PlayerCharacter pcSender, String argString, + AbstractGameObject target) { + String[] args = new String[1]; + args[0] = argString; + + if (pcSender == null) { + return; + } + + this._doCmd(pcSender, args, target); + } + + @Override + protected String _getUsageString() { + return "' /flash message'"; + } + + @Override + protected String _getHelpString() { + return "Flashes system message to all players"; + } + +} diff --git a/src/engine/devcmd/cmds/GateInfoCmd.java b/src/engine/devcmd/cmds/GateInfoCmd.java new file mode 100644 index 00000000..700f2928 --- /dev/null +++ b/src/engine/devcmd/cmds/GateInfoCmd.java @@ -0,0 +1,84 @@ +package engine.devcmd.cmds; + +import engine.Enum.BuildingGroup; +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.math.Vector3fImmutable; +import engine.objects.*; + +public class GateInfoCmd extends AbstractDevCmd { + + public GateInfoCmd() { + super("gateinfo"); + } + + // AbstractDevCmd Overridden methods + + @Override + protected void _doCmd(PlayerCharacter player, String[] args, + AbstractGameObject target) { + + Building targetBuilding; + String outString; + Runegate runeGate; + Blueprint blueprint; + String newline = "\r\n "; + targetBuilding = (Building) target; + + if (targetBuilding.getObjectType() != GameObjectType.Building) { + throwbackInfo(player, "GateInfo: target must be a Building"); + throwbackInfo(player, "Found" + targetBuilding.getObjectType().toString()); + return; + } + + blueprint = Blueprint._meshLookup.get(targetBuilding.getMeshUUID()); + + if (blueprint == null || + (blueprint.getBuildingGroup() != BuildingGroup.RUNEGATE)) { + throwbackInfo(player, "showgate: target must be a Runegate"); + return; + } + + + runeGate = Runegate._runegates.get(targetBuilding.getObjectUUID()); + + outString = "RungateType: " + runeGate.gateBuilding.getName(); + outString += newline; + + outString += "Portal State:"; + outString += newline; + + for (Portal portal : runeGate.getPortals()) { + + outString += "Portal: " + portal.portalType.name(); + outString += " Active: " + portal.isActive(); + outString += " Dest: " + portal.targetGate.getName(); + outString += newline; + outString += " Origin: " + portal.getPortalLocation().x + 'x'; + outString += " " + portal.getPortalLocation().y + 'y'; + outString += newline; + + Vector3fImmutable offset = portal.getPortalLocation().subtract(targetBuilding.getLoc()); + outString += " Offset: " + offset.x + "x " + offset.z + 'y'; + outString += newline; + outString += newline; + + } + outString += newline; + throwbackInfo(player, outString); + } + + @Override + protected String _getHelpString() { + return "Displays a runegate's gate status"; + } + + @Override + protected String _getUsageString() { + + + return "/gateinfo \n"; + } + + +} diff --git a/src/engine/devcmd/cmds/GetBankCmd.java b/src/engine/devcmd/cmds/GetBankCmd.java new file mode 100644 index 00000000..65e4c466 --- /dev/null +++ b/src/engine/devcmd/cmds/GetBankCmd.java @@ -0,0 +1,52 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.VendorDialogMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class GetBankCmd extends AbstractDevCmd { + + public GetBankCmd() { + super("getbank"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (pcSender == null) + return; + + ClientConnection cc = SessionManager.getClientConnection(pcSender); + if (cc == null) + return; + + VendorDialogMsg.getBank(pcSender, null, cc); + this.setTarget(pcSender); //for logging + } + + @Override + protected String _getUsageString() { + return "' /getbank'"; + } + + @Override + protected String _getHelpString() { + return "Opens bank window"; + } + +} diff --git a/src/engine/devcmd/cmds/GetCacheCountCmd.java b/src/engine/devcmd/cmds/GetCacheCountCmd.java new file mode 100644 index 00000000..eaea4c14 --- /dev/null +++ b/src/engine/devcmd/cmds/GetCacheCountCmd.java @@ -0,0 +1,40 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class GetCacheCountCmd extends AbstractDevCmd { + + public GetCacheCountCmd() { + super("getcachecount"); + this.addCmdString("getcachecount"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + DbManager.printCacheCount(pcSender); + } + + @Override + protected String _getUsageString() { + return "' /getcachecount'"; + } + + @Override + protected String _getHelpString() { + return "Get a count of the objects in the cache"; + } + +} diff --git a/src/engine/devcmd/cmds/GetHeightCmd.java b/src/engine/devcmd/cmds/GetHeightCmd.java new file mode 100644 index 00000000..8254a47f --- /dev/null +++ b/src/engine/devcmd/cmds/GetHeightCmd.java @@ -0,0 +1,226 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.HeightMap; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.math.Vector2f; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + +public class GetHeightCmd extends AbstractDevCmd { + + public GetHeightCmd() { + super("getHeight"); + this.addCmdString("height"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + boolean end = true; + + float height = HeightMap.getWorldHeight(pc); + + this.throwbackInfo(pc, "Altitude : " + height); + + this.throwbackInfo(pc, "Character Height: " + pc.getCharacterHeight()); + this.throwbackInfo(pc, "Character Height to start swimming: " + pc.centerHeight); + + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + this.throwbackInfo(pc, "Water Level : " + zone.getSeaLevel()); + this.throwbackInfo(pc, "Character Water Level Above : " + (pc.getCharacterHeight() + height - zone.getSeaLevel())); + + if (end) + return; + + Vector2f gridSquare; + Vector2f gridOffset; + Vector2f parentGrid; + Vector2f parentLoc = new Vector2f(-1, -1); + + Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (currentZone == null) + return; + + Zone parentZone = currentZone.getParent(); + + HeightMap heightMap = currentZone.getHeightMap(); + + + //find the next parents heightmap if the currentzone heightmap is null. + while (heightMap == null) { + + if (currentZone == ZoneManager.getSeaFloor()) { + this.throwbackInfo(pc, "Could not find a heightmap to get height."); + break; + } + + this.throwbackError(pc, "Heightmap does not exist for " + currentZone.getName()); + this.throwbackInfo(pc, "Using parent zone instead: "); + currentZone = currentZone.getParent(); + heightMap = currentZone.getHeightMap(); + } + + + if ((heightMap == null) || (currentZone == ZoneManager.getSeaFloor())) { + this.throwbackInfo(pc, currentZone.getName() + " has no heightmap "); + this.throwbackInfo(pc, "Current altitude: " + currentZone.absY); + return; + } + + Vector2f zoneLoc = ZoneManager.worldToZoneSpace(pc.getLoc(), currentZone); + + Vector3fImmutable seaFloorLocalLoc = ZoneManager.worldToLocal(pc.getLoc(), ZoneManager.getSeaFloor()); + this.throwbackInfo(pc, "SeaFloor Local : " + seaFloorLocalLoc.x + " , " + seaFloorLocalLoc.y); + + + this.throwbackInfo(pc, "Local Zone Location : " + zoneLoc.x + " , " + zoneLoc.y); + Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(pc.getLoc(), currentZone); + Vector3fImmutable parentLocFromCenter = ZoneManager.worldToLocal(pc.getLoc(), currentZone.getParent()); + this.throwbackInfo(pc, "Local Zone Location from center : " + localLocFromCenter); + this.throwbackInfo(pc, "parent Zone Location from center : " + parentLocFromCenter); + + Vector2f parentZoneLoc = ZoneManager.worldToZoneSpace(pc.getLoc(), currentZone.getParent()); + this.throwbackInfo(pc, "Parent Zone Location from Bottom Left : " + parentZoneLoc); + + if ((parentZone != null) && (parentZone.getHeightMap() != null)) { + parentLoc = ZoneManager.worldToZoneSpace(pc.getLoc(), parentZone); + parentGrid = parentZone.getHeightMap().getGridSquare(parentLoc); + } else + parentGrid = new Vector2f(-1, -1); + + gridSquare = heightMap.getGridSquare(zoneLoc); + gridOffset = HeightMap.getGridOffset(gridSquare); + + float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc); + + this.throwbackInfo(pc, currentZone.getName()); + this.throwbackInfo(pc, "Current Grid Square: " + gridSquare.x + " , " + gridSquare.y); + this.throwbackInfo(pc, "Grid Offset: " + gridOffset.x + " , " + gridOffset.y); + this.throwbackInfo(pc, "Parent Grid: " + parentGrid.x + " , " + parentGrid.y); + + if (parentGrid.x != -1) { + float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc); + this.throwbackInfo(pc, "Parent ALTITUDE: " + (parentAltitude)); + this.throwbackInfo(pc, "Parent Interpolation: " + (parentAltitude + parentZone.getWorldAltitude())); + } + this.throwbackInfo(pc, "interpolated height: " + interaltitude); + + this.throwbackInfo(pc, "interpolated height with World: " + (interaltitude + currentZone.getWorldAltitude())); + + float realWorldAltitude = interaltitude + currentZone.getWorldAltitude(); + + //OUTSET + if (parentZone != null) { + float parentXRadius = currentZone.getBounds().getHalfExtents().x; + float parentZRadius = currentZone.getBounds().getHalfExtents().y; + + float offsetX = Math.abs((localLocFromCenter.x / parentXRadius)); + float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius)); + + float bucketScaleX = 100 / parentXRadius; + float bucketScaleZ = 200 / parentZRadius; + + float outsideGridSizeX = 1 - bucketScaleX; //32/256 + float outsideGridSizeZ = 1 - bucketScaleZ; + float weight; + + double scale; + + + if (offsetX > outsideGridSizeX && offsetX > offsetZ) { + weight = (offsetX - outsideGridSizeX) / bucketScaleX; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + + + float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone)); + + parentCenterAltitude += currentZone.getYCoord(); + parentCenterAltitude += interaltitude; + + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + float outsetALt = firstScale + secondScale; + + outsetALt += currentZone.getParent().getAbsY(); + realWorldAltitude = outsetALt; + + } else if (offsetZ > outsideGridSizeZ) { + + weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ; + scale = Math.atan2((.5 - weight) * 3.1415927, 1); + + float scaleChild = (float) ((scale + 1) * .5); + float scaleParent = 1 - scaleChild; + float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc); + float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone)); + + parentCenterAltitude += currentZone.getYCoord(); + parentCenterAltitude += interaltitude; + float firstScale = parentAltitude * scaleParent; + float secondScale = parentCenterAltitude * scaleChild; + float outsetALt = firstScale + secondScale; + + outsetALt += currentZone.getParent().getAbsY(); + realWorldAltitude = outsetALt; + + + } + } + + float strMod = pc.statStrBase - 40; + + strMod *= .00999999998f; + + strMod += 1f; + + float radius = 0; + switch (pc.getRaceID()) { + case 2017: + radius = 3.1415927f; + case 2000: + + + } + strMod *= 1.5707964f; + + strMod += 3.1415927f; + + strMod -= .5f; + + + realWorldAltitude += strMod; + + this.throwbackInfo(pc, "interpolated height with World: " + realWorldAltitude); + + + } + + @Override + protected String _getHelpString() { + return "Temporarily Changes SubRace"; + } + + @Override + protected String _getUsageString() { + return "' /subrace mobBaseID"; + } + +} diff --git a/src/engine/devcmd/cmds/GetMemoryCmd.java b/src/engine/devcmd/cmds/GetMemoryCmd.java new file mode 100644 index 00000000..795c19b7 --- /dev/null +++ b/src/engine/devcmd/cmds/GetMemoryCmd.java @@ -0,0 +1,58 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class GetMemoryCmd extends AbstractDevCmd { + + public GetMemoryCmd() { + super("getmemory"); + } + + public static String getMemoryOutput(long memory) { + String out = ""; + if (memory > 1073741824) + return (memory / 1073741824) + "GB"; + else if (memory > 1048576) + return (memory / 1048576) + "MB"; + else if (memory > 1024) + return (memory / 1048576) + "KB"; + else + return memory + "B"; + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + if (pcSender == null) + return; + + String hSize = getMemoryOutput(Runtime.getRuntime().totalMemory()); + String mhSize = getMemoryOutput(Runtime.getRuntime().maxMemory()); + String fhSize = getMemoryOutput(Runtime.getRuntime().freeMemory()); + + String out = "Heap Size: " + hSize + ", Max Heap Size: " + mhSize + ", Free Heap Size: " + fhSize; + throwbackInfo(pcSender, out); + } + + @Override + protected String _getUsageString() { + return "' /getmemory'"; + } + + @Override + protected String _getHelpString() { + return "lists memory usage"; + } + +} diff --git a/src/engine/devcmd/cmds/GetOffsetCmd.java b/src/engine/devcmd/cmds/GetOffsetCmd.java new file mode 100644 index 00000000..46c75b14 --- /dev/null +++ b/src/engine/devcmd/cmds/GetOffsetCmd.java @@ -0,0 +1,64 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + +public class GetOffsetCmd extends AbstractDevCmd { + + public GetOffsetCmd() { + super("getoffset"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + if (pcSender == null) + return; + + Zone zone = null; + try { + int loadID = Integer.parseInt(words[0]); + zone = ZoneManager.getZoneByZoneID(loadID); + if (zone == null) { + throwbackError(pcSender, "Error: can't find the zone of ID " + loadID + '.'); + return; + } + } catch (Exception e) { + zone = ZoneManager.findSmallestZone(pcSender.getLoc()); + } + + if (zone == null) { + throwbackError(pcSender, "Error: can't find the zone you're in."); + return; + } + + float difX = pcSender.getLoc().x - zone.absX; + float difY = pcSender.getLoc().y - zone.absY; + float difZ = pcSender.getLoc().z - zone.absZ; + + throwbackInfo(pcSender, zone.getName() + ": (x: " + difX + ", y: " + difY + ", z: " + difZ + ')'); + } + + @Override + protected String _getUsageString() { + return "'./getoffset [zoneID]'"; + } + + @Override + protected String _getHelpString() { + return "lists offset from center of zone"; + } + +} diff --git a/src/engine/devcmd/cmds/GetRuneDropRateCmd.java b/src/engine/devcmd/cmds/GetRuneDropRateCmd.java new file mode 100644 index 00000000..300fe6df --- /dev/null +++ b/src/engine/devcmd/cmds/GetRuneDropRateCmd.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + + +public class GetRuneDropRateCmd extends AbstractDevCmd { + + public GetRuneDropRateCmd() { + super("getrunedroprate"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + if (pcSender == null) + return; + + String out = "Depracated"; + throwbackInfo(pcSender, out); + } + + @Override + protected String _getUsageString() { + return "' /getrunedroprate'"; + } + + @Override + protected String _getHelpString() { + return "lists drop rates for runes and contracts in non-hotzone."; + } + +} diff --git a/src/engine/devcmd/cmds/GetVaultCmd.java b/src/engine/devcmd/cmds/GetVaultCmd.java new file mode 100644 index 00000000..c480f761 --- /dev/null +++ b/src/engine/devcmd/cmds/GetVaultCmd.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.VendorDialogMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class GetVaultCmd extends AbstractDevCmd { + + public GetVaultCmd() { + super("getvault"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (pcSender == null) + return; + + ClientConnection cc = SessionManager.getClientConnection(pcSender); + if (cc == null) + return; + + VendorDialogMsg.getVault(pcSender, null, cc); + this.setTarget(pcSender); //for logging + } + + @Override + protected String _getUsageString() { + return "' /getvault'"; + } + + @Override + protected String _getHelpString() { + return "Opens account vault"; + } + +} diff --git a/src/engine/devcmd/cmds/GetZoneCmd.java b/src/engine/devcmd/cmds/GetZoneCmd.java new file mode 100644 index 00000000..dc7c3693 --- /dev/null +++ b/src/engine/devcmd/cmds/GetZoneCmd.java @@ -0,0 +1,67 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + +import java.util.ArrayList; + +public class GetZoneCmd extends AbstractDevCmd { + + public GetZoneCmd() { + super("getzone"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + if (pcSender == null) + return; + + if (words.length != 1) { + this.sendUsage(pcSender); + return; + } + + ArrayList allIn = new ArrayList<>(); + switch (words[0].toLowerCase()) { + case "all": + throwbackInfo(pcSender, "All zones currently in"); + allIn = ZoneManager.getAllZonesIn(pcSender.getLoc()); + break; + case "smallest": + throwbackInfo(pcSender, "Smallest zone currently in"); + Zone zone = ZoneManager.findSmallestZone(pcSender.getLoc()); + allIn.add(zone); + break; + default: + this.sendUsage(pcSender); + return; + } + + for (Zone zone : allIn) + throwbackInfo(pcSender, zone.getName() + "; UUID: " + zone.getObjectUUID() + ", loadNum: " + zone.getLoadNum()); + } + + @Override + protected String _getUsageString() { + return "' /getzone smallest/all'"; + } + + @Override + protected String _getHelpString() { + return "lists what zones a player is in"; + } + +} diff --git a/src/engine/devcmd/cmds/GotoBoundsCmd.java b/src/engine/devcmd/cmds/GotoBoundsCmd.java new file mode 100644 index 00000000..67004d26 --- /dev/null +++ b/src/engine/devcmd/cmds/GotoBoundsCmd.java @@ -0,0 +1,107 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +public class GotoBoundsCmd extends AbstractDevCmd { + + public GotoBoundsCmd() { + super("gotobounds"); + } + + @Override + protected void _doCmd(PlayerCharacter player, String[] words, + AbstractGameObject target) { + + String corner = words[0]; + Vector3fImmutable targetLoc = Vector3fImmutable.ZERO; + + if (target == null || !target.getObjectType().equals(GameObjectType.Building)) { + this.throwbackError(player, "No Building Selected"); + return; + } + + Building building = (Building) target; + + if (building.getBounds() == null) { + this.throwbackInfo(player, "No valid Bounds for building UUID " + building.getObjectUUID()); + return; + } + float x = building.getBounds().getHalfExtents().x; + float z = building.getBounds().getHalfExtents().y; + + if (building.getBlueprint() != null) { + x = building.getBlueprint().getExtents().x; + z = building.getBlueprint().getExtents().y; + } + + float topLeftX = building.getLoc().x - x; + float topLeftY = building.getLoc().z - z; + + float topRightX = building.getLoc().x + x; + float topRightY = building.getLoc().z - z; + + float bottomLeftX = building.getLoc().x - x; + float bottomLeftY = building.getLoc().z + z; + + float bottomRightX = building.getLoc().x + x; + float bottomRightY = building.getLoc().z + z; + + + switch (corner) { + case "topleft": + targetLoc = new Vector3fImmutable(topLeftX, 0, topLeftY); + break; + case "topright": + targetLoc = new Vector3fImmutable(topRightX, 0, topRightY); + break; + case "bottomleft": + targetLoc = new Vector3fImmutable(bottomLeftX, 0, bottomLeftY); + break; + case "bottomright": + targetLoc = new Vector3fImmutable(bottomRightX, 0, bottomRightY); + break; + default: + this.throwbackInfo(player, "wrong corner name. use topleft , topright , bottomleft , bottomright"); + return; + + } + + targetLoc = Vector3fImmutable.transform(building.getLoc(), targetLoc, building.getBounds().getRotationDegrees()); + + // Teleport player + + if (targetLoc == Vector3fImmutable.ZERO) { + this.throwbackError(player, "Failed to locate UUID"); + return; + } + + player.teleport(targetLoc); + + } + + @Override + protected String _getHelpString() { + return "Teleports player to a UUID"; + } + + @Override + protected String _getUsageString() { + return "' /gotoobj '"; + + } + +} diff --git a/src/engine/devcmd/cmds/GotoCmd.java b/src/engine/devcmd/cmds/GotoCmd.java new file mode 100644 index 00000000..95ab5f93 --- /dev/null +++ b/src/engine/devcmd/cmds/GotoCmd.java @@ -0,0 +1,181 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import engine.server.MBServerStatics; + +import java.util.concurrent.ThreadLocalRandom; + +public class GotoCmd extends AbstractDevCmd { + + public GotoCmd() { + super("goto"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + Vector3fImmutable loc = null; + + // Arg Count Check + + + if (target != null && words[0].isEmpty()) { + AbstractWorldObject targetAgo = (AbstractWorldObject) target; + pc.teleport(targetAgo.getLoc()); + return; + } + + if (words[0].isEmpty()) { + this.sendUsage(pc); + return; + } + + if (words[0].equalsIgnoreCase("playground")) { + if (target instanceof AbstractCharacter) { + loc = new Vector3fImmutable(63276, 0, -54718); + } + + if (loc != null) + pc.teleport(loc); + + return; + } + + if (words[0].equalsIgnoreCase("coc")) { + if (target instanceof AbstractCharacter) { + loc = new Vector3fImmutable(98561.656f, 0, -13353.778f); + } + + if (loc != null) + pc.teleport(loc); + + return; + } + + String cityName = ""; + for (String partial : words) { + cityName += partial + ' '; + } + + cityName = cityName.substring(0, cityName.length() - 1); + + for (AbstractGameObject cityAgo : DbManager.getList(GameObjectType.City)) { + City city = (City) cityAgo; + if (city == null) + continue; + if (!city.getCityName().equalsIgnoreCase(cityName)) + continue; + Zone zone = city.getParent(); + if (zone != null) { + if (zone.isNPCCity() || zone.isPlayerCity()) + loc = Vector3fImmutable.getRandomPointOnCircle(zone.getLoc(), MBServerStatics.TREE_TELEPORT_RADIUS); + else + loc = zone.getLoc(); + + int random = ThreadLocalRandom.current().nextInt(5); + if (random == 1) + break; + } + } + + if (loc == null) { + for (AbstractGameObject zoneAgo : DbManager.getList(GameObjectType.Zone)) { + Zone zone = (Zone) zoneAgo; + if (zone == null) + continue; + if (!zone.getName().equalsIgnoreCase(cityName)) + continue; + if (zone != null) { + if (zone.isNPCCity() || zone.isPlayerCity()) + loc = Vector3fImmutable.getRandomPointOnCircle(zone.getLoc(), MBServerStatics.TREE_TELEPORT_RADIUS); + else + loc = zone.getLoc(); + + int random = ThreadLocalRandom.current().nextInt(5); + if (random == 1) + break; + } + } + } + if (loc == null && words.length == 1) { + + try { + PlayerCharacter pcDest = SessionManager + .getPlayerCharacterByLowerCaseName(words[0]); + if (pcDest == null) { + this.throwbackError(pc, "Player or Zone not found by name: " + + words[0]); + this.throwbackInfo(pc, "If you have spaces in the zone name, replace them with '_'"); + return; + } + + if (pcDest.getCombinedName().equals(pc.getCombinedName())) { + this + .throwbackError(pc, + "Cannot goto yourself. Well, you can, but you wont go anywhere."); + return; + } + + loc = pcDest.getLoc(); + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to goto a character named '" + + words[0] + '\''); + return; + } + + } + if (loc == null) { // lat lon mode + if (words.length != 2) { + throwbackError(pc, this.getUsageString()); + return; + } + float lat = 0.0f, lon = 0.0f; + String latLong = '\'' + words[0] + ", " + words[1] + '\''; + + try { + lat = Float.parseFloat(words[0]); + lon = Float.parseFloat(words[1]); + loc = new Vector3fImmutable(lat, 0f, -lon); + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied LatLong: " + latLong + + " failed to parse to Floats"); + return; + + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to goto LatLong of " + + latLong); + return; + } + } + if (loc != null) { + pc.teleport(loc); + } + } + + @Override + protected String _getHelpString() { + return "Alters your characters position TO 'lat' and 'long', or TO the position of 'characterName'. This does not transport you BY 'lat' and 'long', but rather TO 'lat' and 'long' "; + } + + @Override + protected String _getUsageString() { + return "'[ /goto lat lon] || [ /goto characterName] || [/goto zoneName \replace spaces with `_`]`"; + } + +} diff --git a/src/engine/devcmd/cmds/GotoObj.java b/src/engine/devcmd/cmds/GotoObj.java new file mode 100644 index 00000000..e6e69b8c --- /dev/null +++ b/src/engine/devcmd/cmds/GotoObj.java @@ -0,0 +1,102 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; + +public class GotoObj extends AbstractDevCmd { + + public GotoObj() { + super("gotoobj"); + } + + @Override + protected void _doCmd(PlayerCharacter player, String[] words, + AbstractGameObject target) { + + int uuid; + Vector3fImmutable targetLoc = Vector3fImmutable.ZERO; + Enum.DbObjectType objectType; + + try { + uuid = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + this.throwbackError(player, "Failed to parse UUID" + e.toString()); + return; + } + + objectType = DbManager.BuildingQueries.GET_UID_ENUM(uuid); + + switch (objectType) { + + case NPC: + NPC npc = (NPC) DbManager.getFromCache(Enum.GameObjectType.NPC, uuid); + + if (npc != null) + targetLoc = npc.getLoc(); + break; + case MOB: + Mob mob = (Mob) DbManager.getFromCache(Enum.GameObjectType.Mob, uuid); + + if (mob != null) + targetLoc = mob.getLoc(); + break; + case CHARACTER: + PlayerCharacter playerCharacter = (PlayerCharacter) DbManager.getFromCache(Enum.GameObjectType.PlayerCharacter, uuid); + + if (playerCharacter != null) + targetLoc = playerCharacter.getLoc(); + break; + case BUILDING: + Building building = (Building) DbManager.getFromCache(Enum.GameObjectType.Building, uuid); + + if (building != null) + targetLoc = building.getLoc(); + break; + case ZONE: + Zone zone = (Zone) DbManager.getFromCache(Enum.GameObjectType.Zone, uuid); + + if (zone != null) + targetLoc = zone.getLoc(); + break; + case CITY: + City city = (City) DbManager.getFromCache(Enum.GameObjectType.City, uuid); + + if (city != null) + targetLoc = city.getLoc(); + break; + } + // Teleport player + + if (targetLoc == Vector3fImmutable.ZERO) { + this.throwbackError(player, "Failed to locate UUID"); + return; + } + + player.teleport(targetLoc); + + } + + @Override + protected String _getHelpString() { + return "Teleports player to a UUID"; + } + + @Override + protected String _getUsageString() { + return "' /gotoobj '"; + + } + +} diff --git a/src/engine/devcmd/cmds/HelpCmd.java b/src/engine/devcmd/cmds/HelpCmd.java new file mode 100644 index 00000000..b0fe83e3 --- /dev/null +++ b/src/engine/devcmd/cmds/HelpCmd.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DevCmdManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class HelpCmd extends AbstractDevCmd { + + public HelpCmd() { + super("help"); + this.addCmdString("list"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (pcSender == null) + return; + if (pcSender.getAccount() == null) + return; + this.throwbackInfo( + pcSender, + "Type ' /command ?' for info about a command. A space is necessary before the slash."); + String commands = DevCmdManager.getCmdsForAccessLevel(); + this.throwbackInfo(pcSender, "Commands your account is eligible to use: "); + + int first = 0; + int last = 500; + int charLimit = 500; + while (commands.length() > charLimit) { + this.throwbackInfo(pcSender, commands.substring(first, last)); + first = charLimit; + charLimit += 500; + last = charLimit; + } + this.throwbackInfo(pcSender, commands.substring(first)); + } + + @Override + protected String _getUsageString() { + return "' /help' || ' /list'"; + } + + @Override + protected String _getHelpString() { + return "Displays help info and lists all commands accessible for the player's access level."; + } + +} diff --git a/src/engine/devcmd/cmds/HotzoneCmd.java b/src/engine/devcmd/cmds/HotzoneCmd.java new file mode 100644 index 00000000..2aa88f14 --- /dev/null +++ b/src/engine/devcmd/cmds/HotzoneCmd.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +/** + * ./hotzone <- display the current hotzone & time remaining + * ./hotzone random <- change hotzone to random new zone + */ + +public class HotzoneCmd extends AbstractDevCmd { + + public HotzoneCmd() { + super("hotzone"); + } + + @Override + protected void _doCmd(PlayerCharacter playerCharacter, String[] words, + AbstractGameObject target) { + + StringBuilder data = new StringBuilder(); + String outString; + + for (String s : words) { + data.append(s); + data.append(' '); + } + + String input = data.toString().trim(); + + if (input.length() == 0) { + outString = "Current hotZone: " + ZoneManager.hotZone.getName() + "\r\n"; + outString += "Available hotZones: " + ZoneManager.availableHotZones(); + throwbackInfo(playerCharacter, outString); + return; + } + + if (input.equalsIgnoreCase("random")) { + ZoneManager.generateAndSetRandomHotzone(); + outString = "New hotZone: " + ZoneManager.hotZone.getName() + "\r\n"; + outString += "Available hotZones: " + ZoneManager.availableHotZones(); + throwbackInfo(playerCharacter, outString); + return; + } + + if (input.equalsIgnoreCase("reset")) { + ZoneManager.resetHotZones(); + throwbackInfo(playerCharacter, "Available hotZones: " + ZoneManager.availableHotZones()); + return; + } + + return; + } + + @Override + protected String _getHelpString() { + return "Use no arguments to see the current hotzone or \"random\" to change it randomly."; + } + + @Override + protected String _getUsageString() { + return "'./hotzone [random]"; + } + + +} diff --git a/src/engine/devcmd/cmds/InfoCmd.java b/src/engine/devcmd/cmds/InfoCmd.java new file mode 100644 index 00000000..5cb88ef9 --- /dev/null +++ b/src/engine/devcmd/cmds/InfoCmd.java @@ -0,0 +1,547 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.GameObjectType; +import engine.Enum.TargetColor; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.SessionManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import engine.util.StringUtils; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * @author + */ +public class InfoCmd extends AbstractDevCmd { + + public InfoCmd() { + super("info"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + if (pc == null) { + return; + } + + String newline = "\r\n "; + + try { + int targetID = Integer.parseInt(words[0]); + Building b = BuildingManager.getBuilding(targetID); + if (b == null) + throwbackError(pc, "Building with ID " + targetID + + " not found"); + else + target = b; + } catch (Exception e) { + } + + if (target == null) { + throwbackError(pc, "Target is unknown or of an invalid type." + + newline + "Type ID: 0x" + + pc.getLastTargetType().toString() + + " Table ID: " + pc.getLastTargetID()); + return; + } + + + GameObjectType objType = target.getObjectType(); + int objectUUID = target.getObjectUUID(); + String output; + + output = "Target Information:" + newline; + output += StringUtils.addWS("UUID: " + objectUUID, 20); + output += newline; + output += "Type: " + target.getClass().getSimpleName(); + output += " [0x" + objType.toString() + ']'; + + if (target instanceof AbstractWorldObject) { + AbstractWorldObject targetAWO = (AbstractWorldObject) target; + Vector3fImmutable targetLoc = targetAWO.getLoc(); + output += newline; + output += StringUtils.addWS("Lat: " + targetLoc.x, 20); + output += "Lon: " + -targetLoc.z; + output += newline; + output += StringUtils.addWS("Alt: " + targetLoc.y, 20); + output += newline; + output += "Rot: " + targetAWO.getRot().y; + output += newline; + double radian = 0; + + + if (targetAWO.getBounds() != null && targetAWO.getBounds().getQuaternion() != null) + radian = targetAWO.getBounds().getQuaternion().angleY; + int degrees = (int) Math.toDegrees(radian); + + + output += "Degrees: " + degrees; + output += newline; + } + + switch (objType) { + case Building: + Building targetBuilding = (Building) target; + output += StringUtils.addWS("Lac: " + + targetBuilding.getw(), 20); + output += "Blueprint : "; + output += targetBuilding.getBlueprintUUID(); + output += newline; + + output += " MeshUUID : "; + output += targetBuilding.getMeshUUID(); + output += newline; + + if (targetBuilding.getBlueprintUUID() != 0) + output += ' ' + targetBuilding.getBlueprint().getName(); + + output += newline; + output += targetBuilding.getBlueprint() != null ? targetBuilding.getBlueprint().getBuildingGroup().name() : " no building group"; + + output += newline; + output += "EffectFlags: " + targetBuilding.getEffectFlags(); + output += newline; + output += StringUtils.addWS("rank: " + targetBuilding.getRank(), + 20); + output += "HP: " + targetBuilding.getHealth() + '/' + + targetBuilding.getMaxHitPoints(); + output += newline; + output += "Scale: (" + targetBuilding.getMeshScale().getX(); + output += ", " + targetBuilding.getMeshScale().getY(); + output += ", " + targetBuilding.getMeshScale().getZ() + ')'; + output += newline; + output += "Owner UID: " + targetBuilding.getOwnerUUID(); + output += (targetBuilding.isOwnerIsNPC() ? " (NPC)" : " (PC)"); + output += newline; + output += "ProtectionState: " + targetBuilding.getProtectionState().name(); + output += newline; + + if (targetBuilding.getUpgradeDateTime() != null) { + output += targetBuilding.getUpgradeDateTime().toString(); + output += newline; + } + + Guild guild = targetBuilding.getGuild(); + Guild nation = null; + String guildId = "-1"; + String nationId = "-1"; + String gTag = ""; + String nTag = ""; + + if (guild != null) { + int id = guild.getObjectUUID(); + + if (id == 0) { + guildId = id + " [" + guild.hashCode() + ']'; + } else + guildId = Integer.toString(id); + + gTag = guild.getGuildTag().summarySentence(); + nation = guild.getNation(); + + if (nation != null) { + id = nation.getObjectUUID(); + if (id == 0) { + nationId = id + " [" + nation.hashCode() + ']'; + } else { + nationId = Integer.toString(id); + } + nTag = nation.getGuildTag().summarySentence(); + } + } + output += StringUtils.addWS("Guild UID: " + guildId, 20); + + if (gTag.length() > 0) + output += "Guild crest: " + gTag; + + output += newline; + output += StringUtils.addWS("Nation UID: " + nationId, 20); + + if (nTag.length() > 0) { + output += "Nation crest: " + nTag; + } + + output += newline; + + + if (targetBuilding.getBlueprint() != null) { + + if (targetBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.MINE) { + Mine mine = Mine.getMineFromTower(targetBuilding.getObjectUUID()); + + if (mine != null) { + output += newline; + output += "Mine active: " + mine.getIsActive(); + output += newline; + output += "Was claimed: " + mine.wasClaimed; + output += newline; + output += "Mine Type: " + mine.getMineType().name; + output += newline; + output += "Expansion : " + mine.isExpansion(); + output += newline; + output += "Production type: " + mine.getProduction().name(); + + } + } + output += newline; + + if (targetBuilding.maintDateTime != null) { + output += targetBuilding.maintDateTime.toString(); + output += newline; + } + } + + output += "Reserve : " + targetBuilding.reserve; + output += newline; + output += "Strongbox : " + targetBuilding.getStrongboxValue(); + output += newline; + + // List hirelings + + if (targetBuilding.getHirelings().isEmpty() == false) { + + output += newline; + output += "Hirelings List: name / slot / floor"; + + for (AbstractCharacter npc : targetBuilding.getHirelings().keySet()) { + + if (npc.getObjectType() != GameObjectType.NPC) + continue; + output += newline + npc.getName() + " slot " + targetBuilding.getHirelings().get(npc); + output += newline + "location " + npc.getLoc(); + } + } + + ArrayList tempList = BuildingRegions._staticRegions.get(targetBuilding.getMeshUUID()); + output += newline; + output += "Building Regions: Size - " + tempList.size(); + output += newline; + output += "Building Regions from Bounds: Size - " + targetBuilding.getBounds().getRegions().size(); + output += newline; + + for (Regions regions : targetBuilding.getBounds().getRegions()) { + //TODO ADD REGION INFO + } + + break; + case PlayerCharacter: + output += newline; + PlayerCharacter targetPC = (PlayerCharacter) target; + output += StringUtils.addWS("Name: " + targetPC.getName(), 20); + output += newline; + output += "InSession : " + SessionManager.getPlayerCharacterByID(target.getObjectUUID()) != null ? " true " : " false"; + output += newline; + output += "RaceType: " + targetPC.getRace().getRaceType().name(); + output += newline; + output += "Race: " + targetPC.getRace().getName(); + output += newline; + output += "Safe:" + targetPC.inSafeZone(); + output += newline; + output += "Experience : " + targetPC.getExp(); + output += newline; + output += "OverFlowExperience : " + targetPC.getOverFlowEXP(); + output += newline; + output += StringUtils.addWS("Level: " + + targetPC.getLevel() + " (" + + TargetColor.getCon(targetPC, pc).toString() + ')', 20); + + Account acpc = SessionManager.getAccount(pc); + Account ac = SessionManager.getAccount(targetPC); + + if (acpc != null && ac != null) { + output += "Account ID: " + ac.getObjectUUID(); + output += newline; + output += "Access Level: " + ac.status.name(); + } else + output += "Account ID: UNKNOWN"; + + output += newline; + output += "Inventory Weight:" + (targetPC.getCharItemManager().getInventoryWeight() + targetPC.getCharItemManager().getEquipWeight()); + output += newline; + output += "Max Inventory Weight:" + ((int) targetPC.statStrBase * 3); + output += newline; + output += "ALTITUDE :" + targetPC.getAltitude(); + output += newline; + output += "BuildingID :" + targetPC.getInBuildingID(); + output += newline; + output += "inBuilding :" + targetPC.getInBuilding(); + output += newline; + output += "inFloor :" + targetPC.getInFloorID(); + output += newline; + + BaseClass baseClass = targetPC.getBaseClass(); + + if (baseClass != null) + output += StringUtils.addWS("Class: " + baseClass.getName(), 20); + else + output += StringUtils.addWS("", 20); + + PromotionClass promotionClass = targetPC.getPromotionClass(); + if (promotionClass != null) { + output += "Pro. Class: " + promotionClass.getName(); + } else { + output += "Pro. Class: "; + } + + output += newline; + output += "====Guild Info===="; + output += newline; + + if (targetPC.getGuild() != null) { + output += "Name: " + targetPC.getGuild().getName(); + output += newline; + output += "State: " + targetPC.getGuild().getGuildState(); + output += newline; + output += "Realms Owned:" + targetPC.getGuild().getRealmsOwned(); + output += newline; + output += "====Nation===="; + output += newline; + output += "Nation Name: " + targetPC.getGuild().getNation().getName(); + output += newline; + output += "Nation State: " + targetPC.getGuild().getNation().getGuildState(); + output += newline; + output += "Realms Owned:" + targetPC.getGuild().getNation().getRealmsOwned(); + output += newline; + output += "Guild Rank:" + (GuildStatusController.getRank(targetPC.getGuildStatus()) + targetPC.getGuild().getRealmsOwnedFlag()); + } + + output += newline; + output += "Movement State: " + targetPC.getMovementState().name(); + output += newline; + output += "Movement Speed: " + targetPC.getSpeed(); + + output += "Altitude : " + targetPC.getLoc().y; + + output += "Swimming : " + targetPC.isSwimming(); + output += newline; + output += "isMoving : " + targetPC.isMoving(); + + break; + + case NPC: + NPC targetNPC = (NPC) target; + output += "databaseID: " + targetNPC.getDBID() + newline; + output += "Name: " + targetNPC.getName(); + output += newline; + output += StringUtils.addWS("Level: " + targetNPC.getLevel(), 20); + + MobBase mobBase; + + if (targetNPC.getContract() == null) + mobBase = targetNPC.getMobBase(); + else + mobBase = MobBase.getMobBase(targetNPC.getContract().getMobbaseID()); + + if (mobBase != null) { + output += "Mobbbase: " + mobBase.getObjectUUID(); + output += newline; + output += "Flags: " + mobBase.getFlags().toString(); + } + + output += newline; + output += "Spawn: (" + targetNPC.getBindLoc().getX(); + output += ", " + targetNPC.getBindLoc().getY(); + output += ", " + targetNPC.getBindLoc().getZ() + ')'; + output += newline; + output += "ContractID: " + targetNPC.getContractID(); + output += newline; + output += "InventorySet: " + targetNPC.getContract().inventorySet; + output += newline; + output += targetNPC.getContract().getAllowedBuildings().toString(); + output += newline; + output += "Extra Rune: " + targetNPC.getContract().getExtraRune(); + + output += newline; + output += "isTrainer: " + targetNPC.getContract().isTrainer(); + output += newline; + output += "Buy Cost: " + targetNPC.getBuyPercent(); + output += "\tSell Cost: " + targetNPC.getSellPercent(); + output += newline; + output += "fromInit: " + targetNPC.isStatic(); + output += newline; + if (mobBase != null) { + output += newline; + output += "Slottable: " + targetNPC.getContract().getAllowedBuildings().toString(); + output += newline; + output += "EquipSet: " + targetNPC.getEquipmentSetID(); + output += newline; + output += "Parent Zone LoadNum : " + targetNPC.getParentZone().getLoadNum(); + + } + + if (targetNPC.region != null) { + output += newline; + output += "Region found: " + "Building : " + targetNPC.region.parentBuildingID + newline; + output += "building level : " + targetNPC.region.level + newline; + output += "building room : " + targetNPC.region.room + newline; + } else { + output += newline; + output += "No region was found."; + } + + if (targetNPC.getBuilding() != null) { + output += newline; + output += "Building : " + targetNPC.getBuilding(); + } else { + output += newline; + output += "No building found."; + } + break; + case Mob: + Mob targetMob = (Mob) target; + output += "databaseID: " + targetMob.getDBID() + newline; + output += "Name: " + targetMob.getName(); + output += newline; + output += StringUtils.addWS("Level: " + targetMob.getLevel(), 20); + mobBase = targetMob.getMobBase(); + if (mobBase != null) + output += "RaceID: " + mobBase.getObjectUUID(); + else + output += "RaceID: " + targetMob.getLoadID(); + output += newline; + try { + output += "notEnemy: " + targetMob.notEnemy.toString(); + output += newline; + output += "enemy: " + targetMob.enemy.toString(); + output += newline; + } catch (Exception ex) { + //who cares its info + } + output += "Spawn: (" + targetMob.getBindLoc().getX(); + output += ", " + targetMob.getBindLoc().getY(); + output += ", " + targetMob.getBindLoc().getZ() + ')'; + output += newline; + if (targetMob.isPet()) { + output += "isPet: true"; + output += newline; + if ((targetMob.agentType.equals(Enum.AIAgentType.PET))) + output += "isSummonedPet: true"; + else + output += "isSummonedPet: false"; + PlayerCharacter owner = targetMob.getOwner(); + if (owner != null) + output += " owner: " + owner.getObjectUUID(); + output += newline; + output += "assist: " + targetMob.assist + " resting: " + targetMob.isSit(); + output += newline; + } + if (targetMob.getMobBase() != null) { + output += "Mobbase: " + targetMob.getMobBase().getObjectUUID(); + output += newline; + output += "Flags: " + targetMob.getMobBase().getFlags().toString(); + output += newline; + + } + if ((targetMob.agentType.equals(Enum.AIAgentType.MOBILE))) { + output += "SpawnRadius: " + targetMob.getSpawnRadius(); + output += newline; + output += "Spawn Timer: " + targetMob.getSpawnTimeAsString(); + output += newline; + } + output += StringUtils.addWS("isAlive: " + + targetMob.isAlive(), 20); + output += newline; + //output += "Mob State: " + targetMob.state.name(); + + output += newline; + output += "Speed : " + targetMob.getSpeed(); + output += newline; + output += "EquipSet: " + targetMob.equipmentSetID; + output += newline; + try { + output += "Parent Zone LoadNum : " + targetMob.getParentZone().getLoadNum(); + } catch (Exception ex) { + //who cares + } + output += newline; + output += "isMoving : " + targetMob.isMoving(); + if (targetMob.region != null) { + output += newline; + output += "BuildingID : " + targetMob.region.parentBuildingID; + output += "building level : " + targetMob.region.level; + output += "building room : " + targetMob.region.room; + } + if (targetMob.building != null) { + output += "Building Name: " + targetMob.building.getName() + newline; + output += "BuildingID : " + targetMob.building + newline; + output += "Bind Loc : " + targetMob.getBindLoc() + newline; + output += "Curr Loc : " + targetMob.getLoc() + newline; + } else { + output += newline; + output += "No building found." + newline; + } + int max = (int)(4.882 * targetMob.level + 121.0); + if(max > 321){ + max = 321; + } + int min = (int)(4.469 * targetMob.level - 3.469); + output += "Min Loot Roll = " + min; + output += "Max Loot Roll = " + max; + break; + case Item: //intentional passthrough + case MobLoot: + Item item = (Item) target; + ItemBase itemBase = item.getItemBase(); + output += StringUtils.addWS("ItemBase: " + itemBase.getUUID(), 20); + output += "Weight: " + itemBase.getWeight(); + output += newline; + DecimalFormat df = new DecimalFormat("###,###,###,###,##0"); + output += StringUtils.addWS("Qty: " + + df.format(item.getNumOfItems()), 20); + output += "Charges: " + item.getChargesRemaining() + + '/' + item.getChargesMax(); + output += newline; + output += "Name: " + itemBase.getName(); + output += newline; + output += item.getContainerInfo(); + + throwbackInfo(pc, output); + + output = "Effects:" + newline; + ConcurrentHashMap effects = item.getEffects(); + for (String name : effects.keySet()) { + Effect eff = effects.get(name); + output += eff.getEffectsBase().getIDString(); + output += newline; + // output += eff.getEffectToken() + (eff.bakedInStat() ? " (baked in)" : "") + newline; + } + + break; + } + + throwbackInfo(pc, output); + } + + @Override + protected String _getHelpString() { + return "Gets information on an Object."; + } + + @Override + protected String _getUsageString() { + return "' /info targetID'"; + } + +} diff --git a/src/engine/devcmd/cmds/JumpCmd.java b/src/engine/devcmd/cmds/JumpCmd.java new file mode 100644 index 00000000..7da57eb3 --- /dev/null +++ b/src/engine/devcmd/cmds/JumpCmd.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class JumpCmd extends AbstractDevCmd { + + public JumpCmd() { + super("jump"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 2) { + this.sendUsage(pc); + return; + } + + //test + + if (words[0].equalsIgnoreCase("face")) { + + try { + float range = Float.parseFloat(words[1]); + Vector3fImmutable newLoc = pc.getFaceDir().scaleAdd(range, pc.getLoc()); + pc.teleport(newLoc); + + + } catch (NumberFormatException e) { + this.throwbackError(pc, "" + + " failed to parse to Floats"); + return; + + } + return; + } + float lat = 0.0f, lon = 0.0f; + String latLong = '\'' + words[0] + ", " + words[1] + '\''; + + try { + lat = Float.parseFloat(words[0]); + lon = Float.parseFloat(words[1]); + + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied LatLong: " + latLong + + " failed to parse to Floats"); + return; + + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to jump to LatLong of " + + latLong); + return; + } + + Vector3fImmutable loc = pc.getLoc(); + loc = loc.add(lat, 0f, -lon); + pc.teleport(loc); + } + + @Override + protected String _getHelpString() { + return "Alters your characters position by 'lat' and 'long'. This does not transport you TO 'lat' and 'long', but rather BY 'lat' and 'long' "; + + } + + @Override + protected String _getUsageString() { + return "' /jump lat long'"; + } + +} diff --git a/src/engine/devcmd/cmds/MakeBaneCmd.java b/src/engine/devcmd/cmds/MakeBaneCmd.java new file mode 100644 index 00000000..e4328ec0 --- /dev/null +++ b/src/engine/devcmd/cmds/MakeBaneCmd.java @@ -0,0 +1,212 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.ProtectionState; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +/** + * @author Eighty + */ +public class MakeBaneCmd extends AbstractDevCmd { + + public MakeBaneCmd() { + super("makebane"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (words.length < 1 || words.length > 2) { + this.sendUsage(pc); + return; + } + + int attackerID = 0; + int rank = 8; + + if (words.length == 2) { + try { + attackerID = Integer.parseInt(words[0]); + rank = Integer.parseInt(words[1]); + } catch (NumberFormatException e) { + throwbackError(pc, "AttackerGuildID must be a number, " + words[0] + " is invalid"); + return; + } + } else if (words.length == 1) { + if (target == null) { + throwbackError(pc, "No target specified"); + return; + } + + if (!(target instanceof PlayerCharacter)) { + throwbackError(pc, "Target is not a player"); + return; + } + attackerID = target.getObjectUUID(); + + try { + rank = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + throwbackError(pc, "Rank must be specified, 1 through 8"); + return; + } + } + + if (rank < 1 || rank > 8) { + throwbackError(pc, "Rank must be 1 through 8"); + return; + } + + PlayerCharacter player = PlayerCharacter.getPlayerCharacter(attackerID); + + + if (player.getGuild().isEmptyGuild()) { + throwbackError(pc, "Errant's can not place banes"); + return; + } + + AbstractCharacter attackerAGL = Guild.GetGL(player.getGuild()); + + if (attackerAGL == null) { + throwbackError(pc, "No guild leader found for attacking guild."); + return; + } + + if (!(attackerAGL instanceof PlayerCharacter)) { + throwbackError(pc, "Attacking guild leader is an NPC."); + return; + } + + if (player.getGuild().isNPCGuild()) { + throwbackError(pc, "The guild used is an npc guild. They can not bane."); + return; + } + + // if (player.getGuild().getOwnedCity() != null) { + // throwbackError(pc, "The attacking guild already has a city."); + // return; + // } + + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (zone == null) { + throwbackError(pc, "Unable to find the zone you're in."); + return; + } + + if (!zone.isPlayerCity()) { + throwbackError(pc, "This is not a player city."); + return; + } + + City city = City.getCity(zone.getPlayerCityUUID()); + if (city == null) { + throwbackError(pc, "Unable to find the city associated with this zone."); + return; + } + + if (city.getTOL() == null) { + throwbackError(pc, "Unable to find the tree of life for this city."); + return; + } + + if (city.getBane() != null) { + throwbackError(pc, "This city is already baned."); + return; + } + + if (Bane.getBaneByAttackerGuild(player.getGuild()) != null) { + throwbackError(pc, "This guild is already baning someone."); + return; + } + + Blueprint blueprint = Blueprint.getBlueprint(24300); + + if (blueprint == null) { + throwbackError(pc, "Unable to find building set for banestone."); + return; + } + + Vector3f rot = new Vector3f(0, 0, 0); + + // *** Refactor : Overlap test goes here + + //Let's drop a banestone! + Vector3fImmutable localLocation = ZoneManager.worldToLocal(pc.getLoc(), zone); + + if (localLocation == null) { + ChatManager.chatSystemError(pc, "Failed to convert world location to zone location. Contact a CCR."); + Logger.info("Failed to Convert World coordinates to local zone coordinates"); + return; + } + + Building stone = DbManager.BuildingQueries.CREATE_BUILDING( + zone.getObjectUUID(), pc.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(), + localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank, + null, blueprint.getBlueprintUUID(), 1, 0.0f); + + if (stone == null) { + ChatManager.chatSystemError(pc, "Failed to create banestone."); + return; + } + stone.addEffectBit((1 << 19)); + stone.setRank((byte) rank); + stone.setMaxHitPoints(blueprint.getMaxHealth(stone.getRank())); + stone.setCurrentHitPoints(stone.getMaxHitPoints()); + BuildingManager.setUpgradeDateTime(stone, null, 0); + + //Make the bane + + Bane bane = Bane.makeBane(player, city, stone); + + if (bane == null) { + + //delete bane stone, failed to make bane object + DbManager.BuildingQueries.DELETE_FROM_DATABASE(stone); + + throwbackError(pc, "Failed to create bane."); + return; + } + + WorldGrid.addObject(stone, pc); + + //Add baned effect to TOL + city.getTOL().addEffectBit((1 << 16)); + city.getTOL().updateEffects(); + + Vector3fImmutable movePlayerOutsideStone = player.getLoc(); + movePlayerOutsideStone = movePlayerOutsideStone.setX(movePlayerOutsideStone.x + 10); + movePlayerOutsideStone = movePlayerOutsideStone.setZ(movePlayerOutsideStone.z + 10); + player.teleport(movePlayerOutsideStone); + + throwbackInfo(pc, "The city has been succesfully baned."); + } + + @Override + protected String _getHelpString() { + return "Creates an bane."; + } + + @Override + protected String _getUsageString() { + return "'./makebane playerUUID baneRank'"; + } + +} diff --git a/src/engine/devcmd/cmds/MakeItemCmd.java b/src/engine/devcmd/cmds/MakeItemCmd.java new file mode 100644 index 00000000..835cdb27 --- /dev/null +++ b/src/engine/devcmd/cmds/MakeItemCmd.java @@ -0,0 +1,254 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.ItemContainerType; +import engine.Enum.ItemType; +import engine.Enum.OwnerType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.objects.*; +import engine.powers.EffectsBase; + +import java.util.ArrayList; + +/** + * @author Eighty + */ +public class MakeItemCmd extends AbstractDevCmd { + + public MakeItemCmd() { + super("makeitem"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + if (words[0].equals("resources")) { + for (int ibID : Warehouse.getMaxResources().keySet()) { + if (ibID == 7) + continue; + + ItemBase ib = ItemBase.getItemBase(ibID); + + short weight = ib.getWeight(); + if (!pc.getCharItemManager().hasRoomInventory(weight)) { + throwbackError(pc, "Not enough room in inventory for any more of this item"); + + pc.getCharItemManager().updateInventory(); + return; + } + + boolean worked = false; + Item item = new Item(ib, pc.getObjectUUID(), + OwnerType.PlayerCharacter, (byte) 0, (byte) 0, (short) ib.getDurability(), (short) ib.getDurability(), + true, false, ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + + item.setNumOfItems(Warehouse.getMaxResources().get(ibID)); + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + worked = true; + } catch (Exception e) { + throwbackError(pc, "DB error 1: Unable to create item. " + e.getMessage()); + return; + } + + if (item == null || !worked) { + throwbackError(pc, "DB error 2: Unable to create item."); + return; + } + + + //add item to inventory + pc.getCharItemManager().addItemToInventory(item); + } + return; + } + if (words.length < 3 || words.length > 5) { + this.sendUsage(pc); + return; + } + + int quantity = 1; + if (words.length > 3) { + try { + quantity = Integer.parseInt(words[3]); + } catch (NumberFormatException e) { + throwbackError(pc, "Quantity must be a number, " + words[3] + " is invalid"); + return; + } + if (quantity < 1 || quantity > 100) + quantity = 1; + } + + int numItems = 1; + if (words.length > 4) { + try { + numItems = Integer.parseInt(words[4]); + } catch (NumberFormatException e) { + throwbackError(pc, "numResources must be a number, " + words[4] + " is invalid"); + return; + } + numItems = (numItems < 1) ? 1 : numItems; + numItems = (numItems > 5000) ? 5000 : numItems; + } + + int itembaseID; + try { + itembaseID = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + itembaseID = ItemBase.getIDByName(words[0].toLowerCase()); + if (itembaseID == 0) { + throwbackError(pc, "Supplied type " + words[0] + + " failed to parse to an Integer"); + return; + } + } catch (Exception e) { + throwbackError(pc, + "An unknown exception occurred when trying to use createitem command for type " + + words[0]); + return; // NaN + } + + if (itembaseID == 7) { + this.throwbackInfo(pc, "use /addgold to add gold."); + return; + } + + String prefix = ""; + String suffix = ""; + + if (!(words[1].equals("0"))) { + prefix = words[1]; + if (!(prefix.substring(0, 4).equals("PRE-"))) + prefix = EffectsBase.getItemEffectsByName(prefix.toLowerCase()); + if (!(prefix.substring(0, 4).equals("PRE-"))) { + throwbackError(pc, "Invalid Prefix. Prefix must consist of PRE-001 to PRE-334 or 0 for no Prefix."); + return; + } + + boolean validInt = false; + try { + int num = Integer.parseInt(prefix.substring(4, 7)); + if (num > 0 && num < 335) + validInt = true; + } catch (Exception e) { + throwbackError(pc, "error parsing number " + prefix); + } + if (!validInt) { + throwbackError(pc, "Invalid Prefix. Prefix must consist of PRE-001 to PRE-334 or 0 for no Prefix."); + return; + } + } + + if (!(words[2].equals("0"))) { + suffix = words[2]; + + if (!(suffix.substring(0, 4).equals("SUF-"))) + suffix = EffectsBase.getItemEffectsByName(suffix.toLowerCase()); + if (!(suffix.substring(0, 4).equals("SUF-"))) { + throwbackError(pc, "Invalid Suffix. Suffix must consist of SUF-001 to SUF-328 or 0 for no Suffix."); + return; + } + + boolean validInt = false; + try { + int num = Integer.parseInt(suffix.substring(4, 7)); + if (num > 0 && num < 329) + validInt = true; + } catch (Exception e) { + throwbackError(pc, "error parsing number " + suffix); + } + if (!validInt) { + throwbackError(pc, "Invalid Suffix. Suffix must consist of SUF-001 to SUF-328 or 0 for no Suffix."); + return; + } + } + ItemBase ib = ItemBase.getItemBase(itembaseID); + if (ib == null) { + throwbackError(pc, "Unable to find itembase of ID " + itembaseID); + return; + } + + if ((numItems > 1) + && (ib.getType().equals(ItemType.RESOURCE) == false) + && (ib.getType().equals(ItemType.OFFERING)) == false) + numItems = 1; + + CharacterItemManager cim = pc.getCharItemManager(); + if (cim == null) { + throwbackError(pc, "Unable to find the character item manager for player " + pc.getFirstName() + '.'); + return; + } + + byte charges = (byte) ib.getNumCharges(); + short dur = (short) ib.getDurability(); + + String result = ""; + for (int i = 0; i < quantity; i++) { + short weight = ib.getWeight(); + if (!cim.hasRoomInventory(weight)) { + throwbackError(pc, "Not enough room in inventory for any more of this item. " + i + " produced."); + if (i > 0) + cim.updateInventory(); + return; + } + + boolean worked = false; + Item item = new Item(ib, pc.getObjectUUID(), + OwnerType.PlayerCharacter, charges, charges, dur, dur, + true, false, ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + if (numItems > 1) + item.setNumOfItems(numItems); + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + worked = true; + } catch (Exception e) { + throwbackError(pc, "DB error 1: Unable to create item. " + e.getMessage()); + return; + } + + if (item == null || !worked) { + throwbackError(pc, "DB error 2: Unable to create item."); + return; + } + + //create prefix + if (!prefix.isEmpty()) + item.addPermanentEnchantmentForDev(prefix, 0); + + //create suffix + if (!suffix.isEmpty()) + item.addPermanentEnchantmentForDev(suffix, 0); + + //add item to inventory + cim.addItemToInventory(item); + result += " " + item.getObjectUUID(); + } + this.setResult(result); + cim.updateInventory(); + } + + @Override + protected String _getHelpString() { + return "Creates an item of type 'itembaseID' with a prefix and suffix"; + } + + @Override + protected String _getUsageString() { + return "'./makeitem itembaseID PrefixID SuffixID [quantity] [numResources]'"; + } + +} diff --git a/src/engine/devcmd/cmds/MineActiveCmd.java b/src/engine/devcmd/cmds/MineActiveCmd.java new file mode 100644 index 00000000..6f36030a --- /dev/null +++ b/src/engine/devcmd/cmds/MineActiveCmd.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.Mine; +import engine.objects.PlayerCharacter; +import engine.workthreads.HourlyJobThread; + +/** + * + */ +public class MineActiveCmd extends AbstractDevCmd { + + public MineActiveCmd() { + super("mineactive"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + Building mineBuilding = BuildingManager.getBuilding(target.getObjectUUID()); + if (mineBuilding == null) + return; + + Mine mine = Mine.getMineFromTower(mineBuilding.getObjectUUID()); + if (mine == null) + return; + + String trigger = args[0]; + switch (trigger) { + case "true": + HourlyJobThread.mineWindowOpen(mine); + break; + case "false": + HourlyJobThread.mineWindowClose(mine); + break; + default: + this.sendUsage(pcSender); + break; + + } + } + + @Override + protected String _getUsageString() { + return "' /mineactive true|false"; + } + + @Override + protected String _getHelpString() { + return "Temporarily add visual effects to Character"; + } + +} diff --git a/src/engine/devcmd/cmds/NetDebugCmd.java b/src/engine/devcmd/cmds/NetDebugCmd.java new file mode 100644 index 00000000..202dc7b8 --- /dev/null +++ b/src/engine/devcmd/cmds/NetDebugCmd.java @@ -0,0 +1,84 @@ +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + +/** + * @author Summary: Devcmd to toggle logging of application protocol messages + */ + +public class NetDebugCmd extends AbstractDevCmd { + + // Instance variables + + + public NetDebugCmd() { + super("netdebug"); + } + + + // AbstractDevCmd Overridden methods + + private static boolean validateUserInput(String[] userInput) { + + int stringIndex; + String commandSet = "onoff"; + + // incorrect number of arguments test + + if (userInput.length != 1) + return false; + + // Validate arguments + + stringIndex = commandSet.indexOf(userInput[0].toLowerCase()); + + return stringIndex != -1; + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + + Boolean debugState = false; + + if (validateUserInput(args) == false) { + this.sendUsage(pc); + return; + } + + // Arguments have been validated use argument to set debug state + + switch (args[0]) { + case "on": + debugState = true; + break; + case "off": + debugState = false; + break; + default: + break; + } + + MBServerStatics.DEBUG_PROTOCOL = debugState; + + // Send results to user + throwbackInfo(pc, "Network debug state: " + debugState.toString()); + } + + @Override + protected String _getHelpString() { + return "Toggles sending network messages to log"; + } + + // Class methods + + @Override + protected String _getUsageString() { + return "/netdebug on|off"; + } + + +} diff --git a/src/engine/devcmd/cmds/PrintBankCmd.java b/src/engine/devcmd/cmds/PrintBankCmd.java new file mode 100644 index 00000000..7074af52 --- /dev/null +++ b/src/engine/devcmd/cmds/PrintBankCmd.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +import java.util.ArrayList; + +public class PrintBankCmd extends AbstractDevCmd { + + public PrintBankCmd() { + super("printbank"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + AbstractWorldObject tar; + String name; + String type = "PlayerCharacter"; + + if (target != null) { + if (target instanceof AbstractCharacter) { + tar = (AbstractCharacter) target; + name = ((AbstractCharacter) tar).getFirstName(); + } else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + } else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + + if (!(tar instanceof PlayerCharacter)) { + throwbackError(pc, "Must target player"); + return; + } + + + CharacterItemManager cim = ((AbstractCharacter) tar).getCharItemManager(); + ArrayList list = cim.getBank(); + throwbackInfo(pc, "Bank for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); + for (Item item : list) { + throwbackInfo(pc, " " + item.getItemBase().getName() + ", count: " + item.getNumOfItems()); + } + Item gold = cim.getGoldBank(); + if (gold != null) + throwbackInfo(pc, " Gold, count: " + gold.getNumOfItems()); + else + throwbackInfo(pc, " NULL Gold"); + } + + @Override + protected String _getHelpString() { + return "Returns the player's current bank"; + } + + @Override + protected String _getUsageString() { + return "' /printbank'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintBonusesCmd.java b/src/engine/devcmd/cmds/PrintBonusesCmd.java new file mode 100644 index 00000000..e642b712 --- /dev/null +++ b/src/engine/devcmd/cmds/PrintBonusesCmd.java @@ -0,0 +1,90 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.*; +import engine.powers.effectmodifiers.AbstractEffectModifier; + +public class PrintBonusesCmd extends AbstractDevCmd { + + public PrintBonusesCmd() { + super("printbonuses"); + // super("printbonuses", MBServerStatics.ACCESS_LEVEL_ADMIN); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + AbstractWorldObject tar; + String name; + String type = "PlayerCharacter"; + if (target != null) + if (target instanceof Item) { + type = "Item"; + tar = (Item) target; + name = ((Item) tar).getItemBase().getName(); + } else if (target instanceof AbstractCharacter) { + tar = (AbstractCharacter) target; + name = ((AbstractCharacter) tar).getFirstName(); + } else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + + //Get name and type + if (tar instanceof Mob) { + Mob mob = (Mob) tar; + MobBase mb = mob.getMobBase(); + if (mb != null) + name = mb.getFirstName(); + type = "Mob"; + } else if (tar instanceof NPC) { + NPC npc = (NPC) tar; + Contract contract = npc.getContract(); + if (contract != null) + if (contract.isTrainer()) + name = tar.getName() + ", " + contract.getName(); + else + name = tar.getName() + " the " + contract.getName(); + type = "NPC"; + } + + if (tar.getObjectType() == GameObjectType.Item) { + Item targetItem = (Item) tar; + + if (targetItem.getBonuses() != null) + for (AbstractEffectModifier targetName : targetItem.getBonuses().keySet()) { + ChatManager.chatSystemInfo(pc, " " + targetName.modType.name() + "-" + targetName.sourceType.name() + ": " + targetItem.getBonuses().get(name)); + } + } else if (((AbstractCharacter) tar).getBonuses() != null) { + ((AbstractCharacter) tar).getBonuses().printBonusesToClient(pc); + } else + throwbackInfo(pc, "Bonuses for " + type + ' ' + name + " not found"); + } + + @Override + protected String _getHelpString() { + return "Returns the player's current bonuses"; + } + + @Override + protected String _getUsageString() { + return "' /printbonuses'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintEquipCmd.java b/src/engine/devcmd/cmds/PrintEquipCmd.java new file mode 100644 index 00000000..2bf192fe --- /dev/null +++ b/src/engine/devcmd/cmds/PrintEquipCmd.java @@ -0,0 +1,102 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +import java.util.concurrent.ConcurrentHashMap; + +public class PrintEquipCmd extends AbstractDevCmd { + + public PrintEquipCmd() { + super("printequip"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + AbstractWorldObject tar; + String name; + String type = "PlayerCharacter"; + if (target != null) { + if (target instanceof AbstractCharacter) { + tar = (AbstractCharacter) target; + name = ((AbstractCharacter) tar).getFirstName(); + } else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + } else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + + //Get name and type + if (tar instanceof Mob) { + Mob mob = (Mob) tar; + MobBase mb = mob.getMobBase(); + if (mb != null) + name = mb.getFirstName(); + type = "Mob"; + } else if (tar instanceof NPC) { + NPC npc = (NPC) tar; + Contract contract = npc.getContract(); + if (contract != null) { + if (contract.isTrainer()) + name = tar.getName() + ", " + contract.getName(); + else + name = tar.getName() + " the " + contract.getName(); + } + type = "NPC"; + } + + if (tar.getObjectType() == GameObjectType.Mob) { + Mob tarMob = (Mob) tar; + throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); + for (int slot : tarMob.getEquip().keySet()) { + MobEquipment equip = tarMob.getEquip().get(slot); + throwbackInfo(pc, equip.getItemBase().getUUID() + " : " + equip.getItemBase().getName() + ", slot: " + slot); + } + return; + } + + if (tar.getObjectType() == GameObjectType.NPC) { + NPC tarMob = (NPC) tar; + throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); + for (int slot : tarMob.getEquip().keySet()) { + MobEquipment equip = tarMob.getEquip().get(slot); + throwbackInfo(pc, equip.getItemBase().getUUID() + " : " + equip.getItemBase().getName() + ", slot: " + slot); + } + return; + } + + CharacterItemManager cim = ((AbstractCharacter) tar).getCharItemManager(); + ConcurrentHashMap list = cim.getEquipped(); + throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); + for (Integer slot : list.keySet()) { + Item item = list.get(slot); + throwbackInfo(pc, " " + item.getItemBase().getName() + ", slot: " + slot); + } + } + + @Override + protected String _getHelpString() { + return "Returns the player's current equipment"; + } + + @Override + protected String _getUsageString() { + return "' /printequip'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintInventoryCmd.java b/src/engine/devcmd/cmds/PrintInventoryCmd.java new file mode 100644 index 00000000..5535f22e --- /dev/null +++ b/src/engine/devcmd/cmds/PrintInventoryCmd.java @@ -0,0 +1,110 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.ItemType; +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +import java.text.DecimalFormat; +import java.util.ArrayList; + +public class PrintInventoryCmd extends AbstractDevCmd { + + public PrintInventoryCmd() { + super("printinventory"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + if (target == null || (!(target instanceof AbstractCharacter) && !(target instanceof Corpse))) { + target = pc; + } + + + String type = target.getClass().getSimpleName(); + String name = ""; + ArrayList inventory = null; + Item gold = null; + DecimalFormat df = new DecimalFormat("###,###,###,##0"); + + if (target instanceof AbstractCharacter) { + AbstractCharacter tar = (AbstractCharacter) target; + + name = tar.getFirstName(); + + if (tar instanceof Mob) { + Mob mob = (Mob) tar; + MobBase mb = mob.getMobBase(); + if (mb != null) { + name = mb.getFirstName(); + } + } else if (tar instanceof NPC) { + NPC npc = (NPC) tar; + Contract contract = npc.getContract(); + if (contract != null) { + if (contract.isTrainer()) { + name = tar.getName() + ", " + contract.getName(); + } else { + name = tar.getName() + " the " + contract.getName(); + } + } + } + + CharacterItemManager cim = tar.getCharItemManager(); + inventory = cim.getInventory(); //this list can contain gold when tar is a PC that is dead + gold = cim.getGoldInventory(); + throwbackInfo(pc, " Weight : " + (cim.getInventoryWeight() + cim.getEquipWeight())); + } else if (target instanceof Corpse) { + Corpse corpse = (Corpse) target; + name = "of " + corpse.getName(); + inventory = corpse.getInventory(); + } + + throwbackInfo(pc, "Inventory for " + type + ' ' + name + " (" + target.getObjectUUID() + ')'); + + int goldCount = 0; + + for (Item item : inventory) { + if (item.getItemBase().getType().equals(ItemType.GOLD) == false) { + String chargeInfo = ""; + byte chargeMax = item.getChargesMax(); + if (chargeMax > 0) { + byte charges = item.getChargesRemaining(); + chargeInfo = " charges: " + charges + '/' + chargeMax; + } + throwbackInfo(pc, " " + item.getItemBase().getName() + ", count: " + item.getNumOfItems() + chargeInfo); + } else + goldCount += item.getNumOfItems(); + } + if (gold != null) { + goldCount += gold.getNumOfItems(); + } + + if (goldCount > 0 || gold != null) { + throwbackInfo(pc, " Gold, count: " + df.format(goldCount)); + } else { + throwbackInfo(pc, " NULL Gold"); + } + } + + @Override + protected String _getHelpString() { + return "Returns the player's current inventory"; + } + + @Override + protected String _getUsageString() { + return "' /printinventory'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintLocationCmd.java b/src/engine/devcmd/cmds/PrintLocationCmd.java new file mode 100644 index 00000000..053ea33e --- /dev/null +++ b/src/engine/devcmd/cmds/PrintLocationCmd.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Regions; + +/** + * @author Eighty + */ +public class PrintLocationCmd extends AbstractDevCmd { + + public PrintLocationCmd() { + super("printloc"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + PlayerCharacter tar; + + if (target != null && target instanceof PlayerCharacter) + tar = (PlayerCharacter) target; + else + tar = pc; + + throwbackInfo(pc, "Server location for " + tar.getFirstName()); + if (tar.getLoc() != null) { + throwbackInfo(pc, "Lat: " + tar.getLoc().getX()); + throwbackInfo(pc, "Lon: " + -tar.getLoc().getZ()); + throwbackInfo(pc, "Alt: " + tar.getLoc().getY()); + if (pc.region != null) { + this.throwbackInfo(pc, "Player Region Slope Position : " + Regions.SlopeLerpPercent(pc, pc.region)); + this.throwbackInfo(pc, "Region Slope Magnitude : " + Regions.GetMagnitudeOfRegionSlope(pc.region)); + this.throwbackInfo(pc, "Player Region Slope Magnitude : " + Regions.GetMagnitudeOfPlayerOnRegionSlope(pc.region, pc)); + } else { + this.throwbackInfo(pc, "No Region Found for player."); + } + + } else { + throwbackInfo(pc, "Server location for " + tar.getFirstName() + + " not found"); + } + } + + @Override + protected String _getHelpString() { + return "Returns the player's current location according to the server"; + } + + @Override + protected String _getUsageString() { + return "' /printloc'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintPowersCmd.java b/src/engine/devcmd/cmds/PrintPowersCmd.java new file mode 100644 index 00000000..0264439e --- /dev/null +++ b/src/engine/devcmd/cmds/PrintPowersCmd.java @@ -0,0 +1,68 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.CharacterPower; +import engine.objects.PlayerCharacter; +import engine.powers.PowersBase; + +import java.util.concurrent.ConcurrentHashMap; + +public class PrintPowersCmd extends AbstractDevCmd { + + public PrintPowersCmd() { + super("printpowers"); + // super("printpowers", MBServerStatics.ACCESS_LEVEL_ADMIN); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + PlayerCharacter tar; + + if (target != null && target instanceof PlayerCharacter) + tar = (PlayerCharacter) target; + else + tar = pc; + + throwbackInfo(pc, "Server powers for " + tar.getFirstName()); + + ConcurrentHashMap powers = tar.getPowers(); + if (powers != null) { + throwbackInfo(pc, + "Power(token): Trains; TrainsGranted; MaxTrains"); + for (CharacterPower power : powers.values()) { + PowersBase pb = power.getPower(); + if (pb != null) { + throwbackInfo(pc, " " + pb.getName() + '(' + + pb.getToken() + "): " + + power.getTrains() + "; " + + power.getGrantedTrains() + "; " + + pb.getMaxTrains()); + } + } + } else + throwbackInfo(pc, "Powers not found for player"); + } + + @Override + protected String _getHelpString() { + return "Returns the player's current granted powers"; + } + + @Override + protected String _getUsageString() { + return "' /printpowers'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintResistsCmd.java b/src/engine/devcmd/cmds/PrintResistsCmd.java new file mode 100644 index 00000000..2bcfbf46 --- /dev/null +++ b/src/engine/devcmd/cmds/PrintResistsCmd.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +public class PrintResistsCmd extends AbstractDevCmd { + + public PrintResistsCmd() { + super("printresists"); + // super("printresists", MBServerStatics.ACCESS_LEVEL_ADMIN); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + AbstractCharacter tar; + + if (target != null && target instanceof AbstractCharacter) { + tar = (AbstractCharacter) target; + } else + tar = pc; + + //Get name and type + String type = "PlayerCharacter"; + String name = tar.getFirstName(); + if (tar instanceof Mob) { + Mob mob = (Mob) tar; + MobBase mb = mob.getMobBase(); + if (mb != null) + name = mb.getFirstName(); + type = "Mob"; + } else if (tar instanceof NPC) { + NPC npc = (NPC) tar; + Contract contract = npc.getContract(); + if (contract != null) { + if (contract.isTrainer()) + name = tar.getName() + ", " + contract.getName(); + else + name = tar.getName() + " the " + contract.getName(); + } + type = "NPC"; + } + + throwbackInfo(pc, "Server resists for " + type + ' ' + name); + if (tar.getResists() != null) { + tar.getResists().printResistsToClient(pc); + } else + throwbackInfo(pc, "Resists for " + type + ' ' + name + " not found"); + } + + @Override + protected String _getHelpString() { + return "Returns the player's current resists"; + } + + @Override + protected String _getUsageString() { + return "' /printresists'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintSkillsCmd.java b/src/engine/devcmd/cmds/PrintSkillsCmd.java new file mode 100644 index 00000000..51fbe1bc --- /dev/null +++ b/src/engine/devcmd/cmds/PrintSkillsCmd.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +import java.util.concurrent.ConcurrentHashMap; + +public class PrintSkillsCmd extends AbstractDevCmd { + + public PrintSkillsCmd() { + super("printskills"); + // super("printskills", MBServerStatics.ACCESS_LEVEL_ADMIN); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + AbstractCharacter tar = null; + + if (target != null && target instanceof PlayerCharacter) + tar = (PlayerCharacter) target; + else if (target.getObjectType() == GameObjectType.Mob) + tar = (Mob) target; + else + tar = pc; + + throwbackInfo(pc, "Server skills for " + tar.getFirstName()); + ConcurrentHashMap skills = tar.getSkills(); + if (skills != null) { + throwbackInfo(pc, + "Skills Name: Trains; Base(Trains); ModBase(Trains)"); + for (CharacterSkill skill : skills.values()) { + throwbackInfo(pc, " " + skill.getName() + ": " + + skill.getNumTrains() + "; " + + skill.getBaseAmountBeforeMods() + " (" + + skill.getModifiedAmountBeforeMods() + "); " + + skill.getBaseAmount() + " (" + + skill.getModifiedAmount() + '(' + + skill.getTotalSkillPercet() + " )"); + } + } else + throwbackInfo(pc, "Skills not found for player"); + } + + @Override + protected String _getHelpString() { + return "Returns the player's current skills"; + } + + @Override + protected String _getUsageString() { + return "' /printskills'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintStatsCmd.java b/src/engine/devcmd/cmds/PrintStatsCmd.java new file mode 100644 index 00000000..1b6b30ca --- /dev/null +++ b/src/engine/devcmd/cmds/PrintStatsCmd.java @@ -0,0 +1,152 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +import java.util.HashMap; + +/** + * + */ + +public class PrintStatsCmd extends AbstractDevCmd { + + public PrintStatsCmd() { + super("printstats"); + // super("printstats", MBServerStatics.ACCESS_LEVEL_ADMIN); + } + + public static ItemBase getWeaponBase(int slot, HashMap equip) { + if (equip.containsKey(slot)) { + MobEquipment item = equip.get(slot); + if (item != null && item.getItemBase() != null) { + return item.getItemBase(); + } + } + return null; + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + AbstractCharacter tar; + + if (target != null && target instanceof AbstractCharacter) { + tar = (AbstractCharacter) target; + + if (tar instanceof PlayerCharacter) { + printStatsPlayer(pc, (PlayerCharacter) tar); + this.setTarget(tar); //for logging + } else if (tar instanceof Mob) + printStatsMob(pc, (Mob) tar); + else if (tar instanceof NPC) + printStatsNPC(pc, (NPC) tar); + } + } + + public void printStatsPlayer(PlayerCharacter pc, PlayerCharacter tar) { + String newline = "\r\n "; + String out = "Server stats for Player " + tar.getFirstName() + newline; + out += "Unused Stats: " + tar.getUnusedStatPoints() + newline; + out += "Stats Base (Modified)" + newline; + out += " Str: " + (int) tar.statStrBase + " (" + tar.getStatStrCurrent() + ')' + ", maxStr: " + tar.getStrMax() + newline; + out += " Dex: " + (int) tar.statDexBase + " (" + tar.getStatDexCurrent() + ')' + ", maxDex: " + tar.getDexMax() + newline; + out += " Con: " + (int) tar.statConBase + " (" + tar.getStatConCurrent() + ')' + ", maxCon: " + tar.getConMax() + newline; + out += " Int: " + (int) tar.statIntBase + " (" + tar.getStatIntCurrent() + ')' + ", maxInt: " + tar.getIntMax() + newline; + out += " Spi: " + (int) tar.statSpiBase + " (" + tar.getStatSpiCurrent() + ')' + ", maxSpi: " + tar.getSpiMax() + newline; + throwbackInfo(pc, out); + out = "Health: " + tar.getHealth() + ", maxHealth: " + tar.getHealthMax() + newline; + out += "Mana: " + tar.getMana() + ", maxMana: " + tar.getManaMax() + newline; + out += "Stamina: " + tar.getStamina() + ", maxStamina: " + tar.getStaminaMax() + newline; + out += "Defense: " + tar.getDefenseRating() + newline; + out += "Main Hand: atr: " + tar.getAtrHandOne() + ", damage: " + tar.getMinDamageHandOne() + " to " + tar.getMaxDamageHandOne() + ", speed: " + tar.getSpeedHandOne() + newline; + out += "Off Hand: atr: " + tar.getAtrHandTwo() + ", damage: " + tar.getMinDamageHandTwo() + " to " + tar.getMaxDamageHandTwo() + ", speed: " + tar.getSpeedHandTwo() + newline; + out += "isAlive: " + tar.isAlive() + ", Combat: " + tar.isCombat() + newline; + throwbackInfo(pc, out); + } + + public void printStatsMob(PlayerCharacter pc, Mob tar) { + MobBase mb = tar.getMobBase(); + if (mb == null) + return; + + + String newline = "\r\n "; + String out = "Server stats for Mob " + mb.getFirstName() + newline; + out += "Stats Base (Modified)" + newline; + out += " Str: " + tar.getStatStrCurrent() + " (" + tar.getStatStrCurrent() + ')' + newline; + out += " Dex: " + tar.getStatDexCurrent() + " (" + tar.getStatDexCurrent() + ')' + ", maxDex: " + tar.getStatDexCurrent() + newline; + out += " Con: " + tar.getStatConCurrent() + " (" + tar.getStatConCurrent() + ')' + ", maxCon: " + tar.getStatConCurrent() + newline; + out += " Int: " + tar.getStatIntCurrent() + " (" + tar.getStatIntCurrent() + ')' + ", maxInt: " + tar.getStatIntCurrent() + newline; + out += " Spi: " + tar.getStatSpiCurrent() + " (" + tar.getStatSpiCurrent() + ')' + ", maxSpi: " + tar.getStatSpiCurrent() + newline; + + out += "Health: " + tar.getHealth() + ", maxHealth: " + tar.getHealthMax() + newline; + out += "Mana: " + tar.getMana() + ", maxMana: " + tar.getManaMax() + newline; + out += "Stamina: " + tar.getStamina() + ", maxStamina: " + tar.getStaminaMax() + newline; + out += "Defense: " + tar.getDefenseRating() + newline; + + //get weapons + HashMap equip = tar.getEquip(); + ItemBase main = null; + + if (equip != null) + main = getWeaponBase(1, equip); + ItemBase off = null; + + if (equip != null) + getWeaponBase(2, equip); + if (main == null && off == null) { + out += "Main Hand: atr: " + tar.getAtrHandOne() + ", damage: " + tar.getMinDamageHandOne() + " to " + tar.getMaxDamageHandOne() + ", speed: " + tar.getSpeedHandOne() + ", range: 6" + newline; + } else { + if (main != null) + out += "Main Hand: atr: " + tar.getAtrHandOne() + ", damage: " + tar.getMinDamageHandOne() + " to " + tar.getMaxDamageHandOne() + ", speed: " + tar.getSpeedHandOne() + ", range: " + main.getRange() + newline; + if (off != null) + out += "Main Hand: atr: " + tar.getAtrHandTwo() + ", damage: " + tar.getMinDamageHandTwo() + " to " + tar.getMaxDamageHandTwo() + ", speed: " + tar.getSpeedHandTwo() + ", range: " + off.getRange() + newline; + } + out += "isAlive: " + tar.isAlive() + ", Combat: " + tar.isCombat() + newline; + + throwbackInfo(pc, out); + } + + public void printStatsNPC(PlayerCharacter pc, NPC tar) { + Contract contract = tar.getContract(); + if (contract == null) + return; + + String newline = "\r\n "; + + String name; + if (contract != null) { + if (contract.isTrainer()) + name = tar.getName() + ", " + contract.getName(); + else + name = tar.getName() + " the " + contract.getName(); + } else + name = tar.getName(); + String out = "Server stats for NPC " + name + newline; + out += "Sell Percent: " + tar.getSellPercent() + ", Buy Percent: " + tar.getBuyPercent() + newline; + + throwbackInfo(pc, out); + } + + @Override + protected String _getHelpString() { + return "Returns the player's current stats"; + } + + @Override + protected String _getUsageString() { + return "' /printstats'"; + } + +} diff --git a/src/engine/devcmd/cmds/PrintVaultCmd.java b/src/engine/devcmd/cmds/PrintVaultCmd.java new file mode 100644 index 00000000..b949f18d --- /dev/null +++ b/src/engine/devcmd/cmds/PrintVaultCmd.java @@ -0,0 +1,72 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.*; + +import java.util.ArrayList; + +public class PrintVaultCmd extends AbstractDevCmd { + + public PrintVaultCmd() { + super("printvault"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + AbstractWorldObject tar; + String name; + String type = "PlayerCharacter"; + if (target != null) { + if (target instanceof AbstractCharacter) { + tar = (AbstractCharacter) target; + name = ((AbstractCharacter) tar).getFirstName(); + } else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + } else { + tar = pc; + name = ((AbstractCharacter) tar).getFirstName(); + } + + if (!(tar instanceof PlayerCharacter)) { + throwbackError(pc, "Must target player"); + return; + } + + + CharacterItemManager cim = ((AbstractCharacter) tar).getCharItemManager(); + ArrayList list = cim.getVault(); + throwbackInfo(pc, "Vault for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); + for (Item item : list) { + throwbackInfo(pc, " " + item.getItemBase().getName() + ", count: " + item.getNumOfItems()); + } + Item gold = cim.getGoldVault(); + if (gold != null) + throwbackInfo(pc, " Gold, count: " + gold.getNumOfItems()); + else + throwbackInfo(pc, " NULL Gold"); + } + + @Override + protected String _getHelpString() { + return "Returns the player's current vault"; + } + + @Override + protected String _getUsageString() { + return "' /printvault'"; + } + +} diff --git a/src/engine/devcmd/cmds/PullCmd.java b/src/engine/devcmd/cmds/PullCmd.java new file mode 100644 index 00000000..0b26d16b --- /dev/null +++ b/src/engine/devcmd/cmds/PullCmd.java @@ -0,0 +1,102 @@ +package engine.devcmd.cmds; + +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; + +/** + * @author Dev command to move mobile and it's spawn location + * to the player's current location + */ +public class PullCmd extends AbstractDevCmd { + + public PullCmd() { + super("pull"); + } + + private static void MoveMobile(Mob targetMobile, PlayerCharacter pcSender, Vector3fImmutable newLoc, Zone serverZone) { + + Vector3fImmutable localCoords; + + localCoords = ZoneManager.worldToLocal(newLoc, serverZone); + + DbManager.MobQueries.MOVE_MOB(targetMobile.getObjectUUID(), serverZone.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z); + targetMobile.setBindLoc(newLoc); + targetMobile.setLoc(newLoc); + targetMobile.refresh(); + } + + private static void MoveBuilding(Building targetBuilding, PlayerCharacter pcSender, Vector3fImmutable newLoc, Zone serverZone) { + + Vector3fImmutable localCoords; + + localCoords = ZoneManager.worldToLocal(newLoc, serverZone); + + DbManager.BuildingQueries.MOVE_BUILDING(targetBuilding.getObjectUUID(), serverZone.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z); + targetBuilding.setLoc(newLoc); + targetBuilding.getBounds().setBounds(targetBuilding); + targetBuilding.refresh(true); + } + + private static void MoveNPC(NPC targetNPC, PlayerCharacter pcSender, Vector3fImmutable newLoc, Zone serverZone) { + + Vector3fImmutable localCoords; + + localCoords = ZoneManager.worldToLocal(newLoc, serverZone); + + DbManager.NPCQueries.MOVE_NPC(targetNPC.getObjectUUID(), serverZone.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z); + targetNPC.setBindLoc(newLoc); + targetNPC.setLoc(newLoc); + WorldGrid.updateObject(targetNPC, pcSender); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + + Mob targetMobile; + Vector3fImmutable targetLoc; + Zone serverZone; + + if (validateUserInput(pcSender, target, args) == false) { + this.sendUsage(pcSender); + return; + } + + targetLoc = pcSender.getLoc(); + serverZone = ZoneManager.findSmallestZone(targetLoc); + switch (target.getObjectType()) { + case Mob: + MoveMobile((Mob) target, pcSender, targetLoc, serverZone); + break; + case Building: + MoveBuilding((Building) target, pcSender, targetLoc, serverZone); + break; + case NPC: + MoveNPC((NPC) target, pcSender, targetLoc, serverZone); + } + } + + @Override + protected String _getUsageString() { + return "/pull"; + } + + @Override + protected String _getHelpString() { + return "Moves mobile (and spawn) to player's location"; + } + + private boolean validateUserInput(PlayerCharacter pcSender, AbstractGameObject currTarget, String[] userInput) { + + // No target + if (currTarget == null) { + throwbackError(pcSender, "Requires a Mobile be targeted"); + return false; + } + return true; + } +} diff --git a/src/engine/devcmd/cmds/PurgeObjectsCmd.java b/src/engine/devcmd/cmds/PurgeObjectsCmd.java new file mode 100644 index 00000000..7a022384 --- /dev/null +++ b/src/engine/devcmd/cmds/PurgeObjectsCmd.java @@ -0,0 +1,312 @@ +package engine.devcmd.cmds; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.HashSet; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * @author Summary: Game designer utility command to purge all + * objects of a given type within a supplied range + */ + +public class PurgeObjectsCmd extends AbstractDevCmd { + + // Instance variables + + private Vector3fImmutable _currentLocation; + private float _targetRange; + private int _targetMask; + + // Concurrency support + + private ReadWriteLock lock = new ReentrantReadWriteLock(); + + // Constructor + + public PurgeObjectsCmd() { + super("purge"); + } + + private static void PurgeWalls(Zone zone, PlayerCharacter pc) { + + if (!zone.isPlayerCity()) + return; + + for (Building building : zone.zoneBuildingSet) { + if (!BuildingManager.IsWallPiece(building)) + continue; + for (AbstractCharacter ac : building.getHirelings().keySet()) { + NPC npc = null; + Mob mobA = null; + + if (ac.getObjectType() == GameObjectType.NPC) + npc = (NPC) ac; + else if (ac.getObjectType() == GameObjectType.Mob) + mobA = (Mob) ac; + + + if (npc != null) { + for (Mob mob : npc.getSiegeMinionMap().keySet()) { + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + //Mob.getRespawnMap().remove(mob); + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + } + DbManager.NPCQueries.DELETE_NPC(npc); + DbManager.removeFromCache(GameObjectType.NPC, + npc.getObjectUUID()); + WorldGrid.RemoveWorldObject(npc); + } else if (mobA != null) { + for (Mob mob : mobA.getSiegeMinionMap().keySet()) { + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + //Mob.getRespawnMap().remove(mob); + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + } + DbManager.MobQueries.DELETE_MOB(mobA); + DbManager.removeFromCache(GameObjectType.Mob, + mobA.getObjectUUID()); + WorldGrid.RemoveWorldObject(mobA); + } + + } + + + DbManager.BuildingQueries.DELETE_FROM_DATABASE(building); + DbManager.removeFromCache(building); + WorldGrid.RemoveWorldObject(building); + WorldGrid.removeObject(building, pc); + } + + } + + + // AbstractDevCmd Overridden methods + + private static boolean validateUserInput(String[] userInput) { + + int stringIndex; + String commandSet = "npcmobmeshall"; + + // incorrect number of arguments test + + if (userInput.length != 2) + return false; + + // Test of game object type argument + + stringIndex = commandSet.indexOf(userInput[0].toLowerCase()); + + if (stringIndex == -1) + return false; + + // Test if range argument can convert to a float + + try { + Float.parseFloat(userInput[1]); + } catch (NumberFormatException | NullPointerException e) { + return false; + } + + // User input passes validation + + return true; + } + + private static void removeBuilding(PlayerCharacter pc, Building building) { + + if ((building.getBlueprintUUID() != 0) && + (building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL)) + return; + if ((building.getBlueprintUUID() != 0) && + (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) + Shrine.RemoveShrineFromCacheByBuilding(building); + + if ((building.getBlueprint() != null) && (building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE)) + building.disableSpire(false); + + for (AbstractCharacter ac : building.getHirelings().keySet()) { + NPC npc = null; + Mob mobA = null; + + if (ac.getObjectType() == GameObjectType.NPC) + npc = (NPC) ac; + else if (ac.getObjectType() == GameObjectType.Mob) + mobA = (Mob) ac; + + + if (npc != null) { + for (Mob mob : npc.getSiegeMinionMap().keySet()) { + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + //Mob.getRespawnMap().remove(mob); + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + } + DbManager.NPCQueries.DELETE_NPC(npc); + DbManager.removeFromCache(Enum.GameObjectType.NPC, + npc.getObjectUUID()); + WorldGrid.RemoveWorldObject(npc); + } else if (mobA != null) { + for (Mob mob : mobA.getSiegeMinionMap().keySet()) { + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + //Mob.getRespawnMap().remove(mob); + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + } + DbManager.MobQueries.DELETE_MOB(mobA); + DbManager.removeFromCache(Enum.GameObjectType.Mob, + mobA.getObjectUUID()); + WorldGrid.RemoveWorldObject(mobA); + } + + } + + + DbManager.BuildingQueries.DELETE_FROM_DATABASE(building); + DbManager.removeFromCache(building); + WorldGrid.RemoveWorldObject(building); + WorldGrid.removeObject(building, pc); + } + + private static void removeNPC(PlayerCharacter pc, NPC npc) { + DbManager.NPCQueries.DELETE_NPC(npc); + DbManager.removeFromCache(npc); + WorldGrid.RemoveWorldObject(npc); + WorldGrid.removeObject(npc, pc); + } + + // Class methods + + private static void removeMob(PlayerCharacter pc, Mob mob) { + mob.setLoc(Vector3fImmutable.ZERO); //Move it off the plane.. + mob.setBindLoc(Vector3fImmutable.ZERO); //Reset the bind loc.. + //mob.setHealth(-1, pc); //Kill it! + + DbManager.MobQueries.DELETE_MOB(mob); + DbManager.removeFromCache(mob); + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + + // Grab write lock due to use of instance variables + + lock.writeLock().lock(); + + try { + + if (args[0].toLowerCase().equals("walls")) { + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + + PurgeWalls(zone, pc); + return; + } + + if (validateUserInput(args) == false) { + this.sendUsage(pc); + return; + } + + parseUserInput(args); + + // Arguments have been validated and parsed at this point + // Build array of requested objects + + _currentLocation = pc.getLoc(); + + HashSet objectList = + WorldGrid.getObjectsInRangePartial(_currentLocation, _targetRange, _targetMask); + + // Iterate through array and remove objects from game world and database + + for (AbstractWorldObject awo : objectList) { + + switch (awo.getObjectType()) { + case Building: + removeBuilding(pc, (Building) awo); + break; + case NPC: + removeNPC(pc, (NPC) awo); + break; + case Mob: + removeMob(pc, (Mob) awo); + break; + default: + break; + } + } + + // Send results to user + throwbackInfo(pc, "Purge: " + objectList.size() + " objects were removed in range " + _targetRange); + } catch (Exception e) { + Logger.error(e); + } + + // Release Reentrant lock + + finally { + lock.writeLock().unlock(); + } + } + + @Override + protected String _getHelpString() { + return "Purges game objects within range"; + } + + @Override + protected String _getUsageString() { + return "/purge [npc|mob|mesh|all] [range <= 200]"; + } + + private void parseUserInput(String[] userInput) { + + _targetMask = 0; + _targetRange = 0f; + + // Build mask from user input + + switch (userInput[0].toLowerCase()) { + case "npc": + _targetMask = MBServerStatics.MASK_NPC; + break; + case "mob": + _targetMask = MBServerStatics.MASK_MOB; + break; + case "mesh": + _targetMask = MBServerStatics.MASK_BUILDING; + break; + case "all": + _targetMask = MBServerStatics.MASK_NPC | MBServerStatics.MASK_MOB | MBServerStatics.MASK_BUILDING; + break; + default: + break; + } + + // Parse second argument into range parameter. Cap at 200 units. + + _targetRange = Float.parseFloat(userInput[1]); + _targetRange = Math.min(_targetRange, 200f); + } + +} diff --git a/src/engine/devcmd/cmds/RealmInfoCmd.java b/src/engine/devcmd/cmds/RealmInfoCmd.java new file mode 100644 index 00000000..21c29e8d --- /dev/null +++ b/src/engine/devcmd/cmds/RealmInfoCmd.java @@ -0,0 +1,87 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + + +import engine.InterestManagement.RealmMap; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Realm; +import engine.objects.Zone; + +public class RealmInfoCmd extends AbstractDevCmd { + + public RealmInfoCmd() { + super("realminfo"); + } + + @Override + protected void _doCmd(PlayerCharacter playerCharacter, String[] words, + AbstractGameObject target) { + + Zone serverZone; + Realm serverRealm; + int realmID; + String outString = ""; + + if (playerCharacter == null) + return; + + serverZone = ZoneManager.findSmallestZone(playerCharacter.getLoc()); + + if (serverZone == null) { + throwbackError(playerCharacter, "Zone not found"); + return; + } + + realmID = RealmMap.getRealmIDAtLocation(playerCharacter.getLoc()); + + String newline = "\r\n "; + + outString = newline; + outString += "Realm: " + realmID + "("; + + serverRealm = Realm.getRealm(realmID); + + if (serverRealm == null) + outString += "SeaFloor"; + else + outString += serverRealm.getRealmName(); + + outString += ")"; + outString += newline; + + outString += " Zone: " + serverZone.getName(); + + outString += newline; + + if (serverZone.getParent() != null) + outString += " Parent: " + serverZone.getParent().getName(); + else + outString += "Parent: NONE"; + + outString += newline; + + throwbackInfo(playerCharacter, outString); + } + + @Override + protected String _getHelpString() { + return "Returns info on realm."; + } + + @Override + protected String _getUsageString() { + return "' /info targetID'"; + } + +} diff --git a/src/engine/devcmd/cmds/RebootCmd.java b/src/engine/devcmd/cmds/RebootCmd.java new file mode 100644 index 00000000..879125b3 --- /dev/null +++ b/src/engine/devcmd/cmds/RebootCmd.java @@ -0,0 +1,46 @@ +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +/** + * @author Summary: Devcmd to reboot server + */ + +public class RebootCmd extends AbstractDevCmd { + + // Instance variables + + public RebootCmd() { + super("reboot"); + } + + + // AbstractDevCmd Overridden methods + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + + try { + Runtime rt = Runtime.getRuntime(); + rt.exec("./mbrestart.sh"); + } catch (java.io.IOException err) { + Logger.info(err.getMessage()); + } + + } + + @Override + protected String _getHelpString() { + return "Reboot server"; + } + + @Override + protected String _getUsageString() { + return "./reboot"; + } + +} diff --git a/src/engine/devcmd/cmds/RegionCmd.java b/src/engine/devcmd/cmds/RegionCmd.java new file mode 100644 index 00000000..ca19fd25 --- /dev/null +++ b/src/engine/devcmd/cmds/RegionCmd.java @@ -0,0 +1,78 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +import java.lang.reflect.Field; + +public class RegionCmd extends AbstractDevCmd { + + public RegionCmd() { + super("region"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + + if (pc.region == null) { + this.throwbackInfo(pc, "No Region Found."); + return; + } + + + String newLine = System.getProperty("line.separator"); + String result = ""; + result += (pc.region.getClass().getSimpleName()); + result += (" {"); + result += (newLine); + Field[] fields = pc.region.getClass().getDeclaredFields(); + + //print field names paired with their values + for (Field field : fields) { + field.setAccessible(true); + result += (" "); + try { + + if (field.getName().contains("Furniture")) + continue; + result += (field.getName()); + result += (": "); + //requires access to private field: + result += (field.get(pc.region).toString()); + } catch (IllegalAccessException ex) { + System.out.println(ex); + } + result.trim(); + result += (newLine); + } + result += ("}"); + + this.throwbackInfo(pc, result.toString()); + + + } + + @Override + protected String _getHelpString() { + return "Temporarily Changes SubRace"; + } + + @Override + protected String _getUsageString() { + return "' /setBuildingCollidables add/remove 'add creates a collision line.' needs 4 integers. startX, endX, startY, endY"; + + } + +} diff --git a/src/engine/devcmd/cmds/RemoveBaneCmd.java b/src/engine/devcmd/cmds/RemoveBaneCmd.java new file mode 100644 index 00000000..31994552 --- /dev/null +++ b/src/engine/devcmd/cmds/RemoveBaneCmd.java @@ -0,0 +1,69 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.SiegeResult; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.*; + +/** + * @author Eighty + */ +public class RemoveBaneCmd extends AbstractDevCmd { + + public RemoveBaneCmd() { + super("removebane"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (zone == null) { + throwbackError(pc, "Unable to find the zone you're in."); + return; + } + + if (!zone.isPlayerCity()) { + throwbackError(pc, "This is not a player city."); + return; + } + + City city = City.getCity(zone.getPlayerCityUUID()); + if (city == null) { + throwbackError(pc, "Unable to find the city associated with this zone."); + return; + } + + Bane bane = city.getBane(); + if (bane == null) { + throwbackError(pc, "Could not find bane to remove."); + return; + } + + bane.endBane(SiegeResult.DEFEND); + + throwbackInfo(pc, "The bane has been removed."); + } + + @Override + protected String _getHelpString() { + return "Removes a bane from the city grid you're standing on."; + } + + @Override + protected String _getUsageString() { + return "'./removebane'"; + } + +} diff --git a/src/engine/devcmd/cmds/RemoveObjectCmd.java b/src/engine/devcmd/cmds/RemoveObjectCmd.java new file mode 100644 index 00000000..6d13d2b1 --- /dev/null +++ b/src/engine/devcmd/cmds/RemoveObjectCmd.java @@ -0,0 +1,265 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.BuildingGroup; +import engine.Enum.DbObjectType; +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; + +/** + * + */ +public class RemoveObjectCmd extends AbstractDevCmd { + + public RemoveObjectCmd() { + //set to Player access level so it can be run by non-admins on production. + //Actual access level is set in _doCmd. + super("remove"); + this.addCmdString("delete"); + } + + @Override + protected void _doCmd(PlayerCharacter player, String[] words, AbstractGameObject target) { + + int targetID; + DbObjectType targetObjType; + Building targetBuilding; + NPC targetNPC; + Mob targetMob; + + if (target == null && words.length != 1) { + this.sendUsage(player); + return; + } + + // Delete the targeted building + + if (target != null) { + + switch (target.getObjectType()) { + + case Building: + removeBuilding(player, (Building) target); + break; + case NPC: + removeNPC(player, (NPC) target); + break; + case Mob: + removeMob(player, (Mob) target); + break; + default: + throwbackError(player, "Target " + target.getObjectType() + + " is not a valid object type"); + break; + } + return; + } + + // Attempt to delete object based upon parsed UUID from input + + // Parse Target UUID + + try { + targetID = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + throwbackError(player, "Supplied object ID " + words[0] + + " failed to parse to an Integer"); + return; + } + + // Determine object type of given UUID + + targetObjType = DbManager.BuildingQueries.GET_UID_ENUM(targetID); + + // Process accordingly + + switch (targetObjType) { + case BUILDING: + targetBuilding = BuildingManager.getBuilding(targetID); + removeBuilding(player, targetBuilding); + break; + case NPC: + targetNPC = NPC.getNPC(targetID); + removeNPC(player, targetNPC); + break; + case MOB: + targetMob = Mob.getMob(targetID); + removeMob(player, targetMob); + break; + default: + throwbackError(player, "Invalid UUID: Not found in database"); + break; + } + + } + + @Override + protected String _getHelpString() { + return "Removes targeted or specified object"; + } + + @Override + protected String _getUsageString() { + return "' /remove [objectID]' || ' /delete [objectID]'"; + } + + private void removeBuilding(PlayerCharacter pc, Building building) { + + Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (currentZone == null) { + this.throwbackError(pc, "Could not locate zone for player."); + return; + } + + if ((building.getBlueprint() != null) && (building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE)) + building.disableSpire(false); + + if ((building.getBlueprint() != null) && (building.getBlueprint().getBuildingGroup() == BuildingGroup.WAREHOUSE)) { + City city = City.getCity(building.getParentZone().getPlayerCityUUID()); + if (city != null) { + city.setWarehouseBuildingID(0); + } + Warehouse.warehouseByBuildingUUID.remove(building.getObjectUUID()); + } + + + //remove cached shrines. + if ((building.getBlueprintUUID() != 0) + && (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) + Shrine.RemoveShrineFromCacheByBuilding(building); + + for (AbstractCharacter ac : building.getHirelings().keySet()) { + NPC npc = null; + Mob mobA = null; + + if (ac.getObjectType() == GameObjectType.NPC) + npc = (NPC) ac; + else if (ac.getObjectType() == GameObjectType.Mob) + mobA = (Mob) ac; + + if (npc != null) { + for (Mob mob : npc.getSiegeMinionMap().keySet()) { + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + //Mob.getRespawnMap().remove(mob); + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + } + DbManager.NPCQueries.DELETE_NPC(npc); + DbManager.removeFromCache(npc); + WorldGrid.RemoveWorldObject(npc); + WorldGrid.removeObject(npc, pc); + } else if (mobA != null) { + for (Mob mob : mobA.getSiegeMinionMap().keySet()) { + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + //Mob.getRespawnMap().remove(mob); + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + } + DbManager.MobQueries.DELETE_MOB(mobA); + DbManager.removeFromCache(mobA); + WorldGrid.RemoveWorldObject(mobA); + WorldGrid.removeObject(mobA, pc); + } + + + } + Zone zone = building.getParentZone(); + DbManager.BuildingQueries.DELETE_FROM_DATABASE(building); + DbManager.removeFromCache(building); + zone.zoneBuildingSet.remove(building); + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) { + building.RemoveFromBarracksList(); + } + WorldGrid.RemoveWorldObject(building); + WorldGrid.removeObject(building, pc); + + ChatManager.chatSayInfo(pc, + "Building with ID " + building.getObjectUUID() + " removed"); + this.setResult(String.valueOf(building.getObjectUUID())); + } + + private void removeNPC(PlayerCharacter pc, NPC npc) { + + Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc()); + if (currentZone == null) { + this.throwbackError(pc, "Could not locate zone for player."); + return; + } + + // Remove npc from hirelings list. + + if (npc.building != null) + npc.building.getHirelings().remove(npc); + + for (Mob mob : npc.getSiegeMinionMap().keySet()) { + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + } + + DbManager.NPCQueries.DELETE_NPC(npc); + DbManager.removeFromCache(npc); + WorldGrid.RemoveWorldObject(npc); + WorldGrid.removeObject(npc, pc); + ChatManager.chatSayInfo(pc, + "NPC with ID " + npc.getDBID() + " removed"); + this.setResult(String.valueOf(npc.getDBID())); + } + + private void removeMob(PlayerCharacter pc, Mob mob) { + + Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (currentZone == null) { + this.throwbackError(pc, "Could not locate zone for player."); + return; + } + + if (mob.getParentZone() != null && mob.getParentZone() != currentZone && !mob.isPet() && !mob.isNecroPet()) { + this.throwbackError(pc, "Error 376954: Could not Remove Mob.Mob is not in the same zone as player."); + return; + } + + // Remove npc from hirelings list. + + if (mob.building != null) + mob.building.getHirelings().remove(mob); + + mob.setLoc(Vector3fImmutable.ZERO); //Move it off the plane.. + mob.setBindLoc(Vector3fImmutable.ZERO); //Reset the bind loc.. + //mob.setHealth(-1, pc); //Kill it! + + DbManager.MobQueries.DELETE_MOB(mob); + + DbManager.removeFromCache(mob); + WorldGrid.RemoveWorldObject(mob); + WorldGrid.removeObject(mob, pc); + + if (mob.getParentZone() != null) + mob.getParentZone().zoneMobSet.remove(mob); + + ChatManager.chatSayInfo(pc, + "Mob with ID " + mob.getDBID() + " removed"); + this.setResult(String.valueOf(mob.getDBID())); + } + +} diff --git a/src/engine/devcmd/cmds/RenameCmd.java b/src/engine/devcmd/cmds/RenameCmd.java new file mode 100644 index 00000000..d1d31edb --- /dev/null +++ b/src/engine/devcmd/cmds/RenameCmd.java @@ -0,0 +1,79 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.NPC; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class RenameCmd extends AbstractDevCmd { + + public RenameCmd() { + super("rename"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (args.length < 1) { + this.sendUsage(pcSender); + return; + } + if (args[0].isEmpty()) { + throwbackError(pcSender, "Invalid rename Command. must specify a name."); + return; + } + NPC npc = null; + Building building = null; + + if (target != null) { + if (target instanceof NPC) + npc = (NPC) target; + else if (target instanceof Building) + building = (Building) target; + } else + npc = getTargetAsNPC(pcSender); + if (npc != null) { + DbManager.NPCQueries.SET_PROPERTY(npc, "npc_name", args[0]); + String name = args[0]; + name = name.replaceAll("_", " "); + + npc.setName(name); + + this.setResult(String.valueOf(npc.getDBID())); + +// npc.updateDatabase(); + WorldGrid.updateObject(npc, pcSender); + } else if (building != null) { + String name = args[0]; + name = name.replaceAll("_", " "); + building.setName(name); + } + throwbackError(pcSender, "Invalid rename Command. must target an npc."); + } + + @Override + protected String _getUsageString() { + return "' /rename npcName'"; + } + + @Override + protected String _getHelpString() { + return "Renames an NPC."; + } + +} diff --git a/src/engine/devcmd/cmds/ResetLevelCmd.java b/src/engine/devcmd/cmds/ResetLevelCmd.java new file mode 100644 index 00000000..aa6d1ef2 --- /dev/null +++ b/src/engine/devcmd/cmds/ResetLevelCmd.java @@ -0,0 +1,35 @@ +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class ResetLevelCmd extends AbstractDevCmd { + + public ResetLevelCmd() { + super("resetlevel"); + } + + // AbstractDevCmd Overridden methods + + @Override + protected void _doCmd(PlayerCharacter player, String[] args, + AbstractGameObject target) { + + player.ResetLevel(Short.parseShort(args[0])); + } + + @Override + protected String _getHelpString() { + return "Resets character level to `level`. All training points are reset. Player must relog for changes to update."; + } + + @Override + protected String _getUsageString() { + + + return "/resetlevel "; + } + + +} diff --git a/src/engine/devcmd/cmds/RotateCmd.java b/src/engine/devcmd/cmds/RotateCmd.java new file mode 100644 index 00000000..fe25ad35 --- /dev/null +++ b/src/engine/devcmd/cmds/RotateCmd.java @@ -0,0 +1,251 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.objects.*; + +public class RotateCmd extends AbstractDevCmd { + + public RotateCmd() { + super("rotate"); + this.addCmdString("rot"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (target == null && (words.length != 2)) { + this.sendUsage(pc); + return; + } + + + if (words.length == 3) { + try { + + } catch (Exception e) { + + } + } + + + float rot; + if (target != null && words.length == 1) { + + try { + if (words[0].equalsIgnoreCase("face")) { + this.rotateFace(pc, target); + return; + } + + rot = Float.parseFloat(words[0]); + } catch (NumberFormatException e) { + throwbackError(pc, "Supplied rotation " + words[0] + + " failed to parse to a Float"); + return; + } catch (Exception e) { + throwbackError(pc, + "Invalid Rotate Command. Need Rotation specified."); + return; + } + + Vector3f rotation = new Vector3f(0f, rot, 0f); + + if (target instanceof Building) + rotateBuilding(pc, (Building) target, rotation, rot, false); + else if (target instanceof NPC) + rotateNPC(pc, (NPC) target, rotation, false); + else if (target instanceof Mob) + rotateMob(pc, (Mob) target, rotation, false); + else + throwbackError(pc, "Target " + target.getObjectType() + + " is not a valid object type"); + } else { + + int id = 0; + if (words.length == 2) { + try { + id = Integer.parseInt(words[0]); + + if (words[1].equalsIgnoreCase("face")) { + + Building b; + if (id != 0) + b = BuildingManager.getBuilding(id); + else + b = getTargetAsBuilding(pc); + if (b != null) { + rotateFace(pc, b); + return; + } + + // building failed, try npc + NPC npc; + if (id != 0) + npc = NPC.getNPC(id); + else + npc = getTargetAsNPC(pc); + if (npc != null) { + rotateFace(pc, npc); + return; + } + + // NPC failed, try mob + Mob mob; + if (id != 0) + mob = Mob.getMob(id); + else + mob = getTargetAsMob(pc); + if (mob != null) { + rotateFace(pc, mob); + return; + } + throwbackError(pc, "Nothing found to rotate."); + return; + } + rot = Float.parseFloat(words[1]); + } catch (NumberFormatException e) { + throwbackError(pc, "Supplied arguments " + words[0] + ' ' + + words[1] + " failed to parse"); + return; + } catch (Exception e) { + throwbackError(pc, + "Invalid Rotate Command. Need Rotation specified."); + return; // NaN + } + } else { + try { + rot = Float.parseFloat(words[0]); + } catch (NumberFormatException e) { + throwbackError(pc, "Supplied rotation " + words[0] + + " failed to parse to a Float"); + return; + } catch (Exception e) { + throwbackError(pc, + "Invalid Rotate Command. Need Rotation specified."); + return; // NaN + } + } + + Vector3f rotation = new Vector3f(0f, rot, 0f); + + Building b; + if (id != 0) + b = BuildingManager.getBuilding(id); + else + b = getTargetAsBuilding(pc); + if (b != null) { + rotateBuilding(pc, b, rotation, rot, false); + return; + } + + // building failed, try npc + NPC npc; + if (id != 0) + npc = NPC.getNPC(id); + else + npc = getTargetAsNPC(pc); + if (npc != null) { + rotateNPC(pc, npc, rotation, false); + return; + } + + // NPC failed, try mob + Mob mob; + if (id != 0) + mob = Mob.getMob(id); + else + mob = getTargetAsMob(pc); + if (mob != null) { + rotateMob(pc, mob, rotation, false); + return; + } + throwbackError(pc, "Nothing found to rotate."); + } + } + + @Override + protected String _getHelpString() { + return "Rotates targeted or specified object"; + } + + @Override + protected String _getUsageString() { + return "' /rotate [objectID] rotation' || ' /rot [objectID] rotation'"; + } + + private void rotateBuilding(PlayerCharacter pc, Building building, Vector3f rot, float orig, boolean faceDirection) { + if (!faceDirection) + rot.set(0.0f, (float) Math.sin(Math.toRadians(orig) / 2), 0.0f); + building.setRot(rot); + building.setw((float) Math.abs(Math.cos(Math.toRadians(orig) / 2))); + building.getBounds().setBounds(building); + WorldGrid.updateObject(building, pc); + DbManager.BuildingQueries.SET_PROPERTY(building, "rotY", building.getRot().getY()); + DbManager.BuildingQueries.SET_PROPERTY(building, "w", building.getw()); + ChatManager.chatSayInfo(pc, + "Building with ID " + building.getObjectUUID() + " rotated"); + } + + private void rotateNPC(PlayerCharacter pc, NPC npc, Vector3f rot, boolean faceDirection) { + npc.setRot(rot); + DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", rot.y); + WorldGrid.updateObject(npc, pc); + //no rotation for npc's in db currently + ChatManager.chatSayInfo(pc, + "NPC with ID " + npc.getObjectUUID() + " rotated"); + } + + private void rotateMob(PlayerCharacter pc, Mob mob, Vector3f rot, boolean faceDirection) { + mob.setRot(rot); + DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", rot.y); + WorldGrid.updateObject(mob, pc); + //no rotation for mobs's in db currently + ChatManager.chatSayInfo(pc, + "Mob with ID " + mob.getObjectUUID() + " rotated"); + } + + private void rotateFace(PlayerCharacter pc, AbstractGameObject target) { + AbstractWorldObject awo = (AbstractWorldObject) target; + if (awo == null) + return; + Vector3fImmutable buildingLoc = awo.getLoc(); + Vector3fImmutable playerLoc = pc.getLoc(); + + Vector3fImmutable faceDirection = playerLoc.subtract2D(buildingLoc); + + float rotangle = faceDirection.getRotation(); + + float rot = (float) Math.toDegrees(rotangle); + + if (rot > 180) + rot *= -1; + + Vector3f buildingrotation = new Vector3f(0f, rot, 0f); + Vector3f rotation = new Vector3f(0f, rotangle, 0f); + if (target instanceof Building) + rotateBuilding(pc, (Building) target, buildingrotation, rot, false); + else if (target instanceof NPC) + rotateNPC(pc, (NPC) target, rotation, true); + else if (target instanceof Mob) + rotateMob(pc, (Mob) target, rotation, true); + else + throwbackError(pc, "Target " + target.getObjectType() + + " is not a valid object type"); + + } +} \ No newline at end of file diff --git a/src/engine/devcmd/cmds/SetAdminRuneCmd.java b/src/engine/devcmd/cmds/SetAdminRuneCmd.java new file mode 100644 index 00000000..f8df7016 --- /dev/null +++ b/src/engine/devcmd/cmds/SetAdminRuneCmd.java @@ -0,0 +1,84 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.InterestManager; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.CharacterRune; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class SetAdminRuneCmd extends AbstractDevCmd { + + public SetAdminRuneCmd() { + super("setadminrune"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + int runeID = 0; + boolean add = true; + try { + runeID = Integer.parseInt(args[0]); + if (args.length > 1) + add = (args[1].toLowerCase().equals("false")) ? false : true; + } catch (NumberFormatException e) { + this.sendUsage(pcSender); + return; + } + if (runeID < 2901 || runeID > 2911) { + throwbackError(pcSender, + "Invalid setrune Command. must specify an ID between 2901 and 2911."); + return; + } + + if (!(target instanceof PlayerCharacter)) { + target = pcSender; + } + + boolean worked = false; + if (add) { + worked = CharacterRune.grantRune((PlayerCharacter) target, runeID); + if (worked) + ChatManager.chatSayInfo(pcSender, + "rune of ID " + runeID + " added"); + else + throwbackError(pcSender, "Failed to add the rune of type " + + runeID); + } else { + worked = CharacterRune.removeRune((PlayerCharacter) target, runeID); + if (worked) { + ChatManager.chatSayInfo(pcSender, + "rune of ID " + runeID + " removed"); + InterestManager.reloadCharacter(pcSender); + } else + throwbackError(pcSender, "Failed to remove the rune of type " + + runeID); + } + this.setTarget(target); //for logging + + } + + @Override + protected String _getUsageString() { + return "' /setrune runeID [true/false]'"; + } + + @Override + protected String _getHelpString() { + return "Grant or remove the rune with the specified runeID"; + } + +} diff --git a/src/engine/devcmd/cmds/SetBaneActiveCmd.java b/src/engine/devcmd/cmds/SetBaneActiveCmd.java new file mode 100644 index 00000000..eb24655b --- /dev/null +++ b/src/engine/devcmd/cmds/SetBaneActiveCmd.java @@ -0,0 +1,79 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.*; + +/** + * @author Eighty + */ +public class SetBaneActiveCmd extends AbstractDevCmd { + + public SetBaneActiveCmd() { + super("setbaneactive"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + boolean setActive = false; + if (words[0].equals("true")) + setActive = true; + + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (zone == null) { + throwbackError(pc, "Unable to find the zone you're in."); + return; + } + + if (!zone.isPlayerCity()) { + throwbackError(pc, "This is not a player city."); + return; + } + + City city = City.getCity(zone.getPlayerCityUUID()); + if (city == null) { + throwbackError(pc, "Unable to find the city associated with this zone."); + return; + } + + Bane bane = city.getBane(); + if (bane == null) { + throwbackError(pc, "Could not find bane to modify."); + return; + } + + bane.getCity().protectionEnforced = !setActive; + + if (setActive) + throwbackInfo(pc, "The bane has been set active."); + else + throwbackInfo(pc, "The bane has been set inactive."); + } + + @Override + protected String _getHelpString() { + return "Sets a bane active or deactivates a bane."; + } + + @Override + protected String _getUsageString() { + return "'./setbaneactive true|false'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetBaseClassCmd.java b/src/engine/devcmd/cmds/SetBaseClassCmd.java new file mode 100644 index 00000000..be99f8de --- /dev/null +++ b/src/engine/devcmd/cmds/SetBaseClassCmd.java @@ -0,0 +1,64 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.InterestManager; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class SetBaseClassCmd extends AbstractDevCmd { + + public SetBaseClassCmd() { + super("setBaseClass"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + int classID = 0; + try { + classID = Integer.parseInt(words[0]); + } catch (Exception e) { + throwbackError(pc, + "Invalid setBaseClass Command. must specify an ID between 2500 and 2503."); + return; + } + if (classID < 2500 || classID > 2503) { + throwbackError(pc, + "Invalid setBaseClass Command. must specify an ID between 2500 and 2503."); + return; + } + pc.setBaseClass(classID); + this.setTarget(pc); //for logging + ChatManager.chatSayInfo(pc, + "BaseClass changed to " + classID); + InterestManager.reloadCharacter(pc); + + } + + @Override + protected String _getHelpString() { + return "Sets your character's BaseClass to 'id'"; + } + + @Override + protected String _getUsageString() { + return "' /setBaseClass id'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetBuildingAltitudeCmd.java b/src/engine/devcmd/cmds/SetBuildingAltitudeCmd.java new file mode 100644 index 00000000..48386d00 --- /dev/null +++ b/src/engine/devcmd/cmds/SetBuildingAltitudeCmd.java @@ -0,0 +1,106 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +public class SetBuildingAltitudeCmd extends AbstractDevCmd { + + public SetBuildingAltitudeCmd() { + super("setbuildingaltitude"); + this.addCmdString("buildingaltitude"); + } + + private static boolean UpdateBuildingAltitude(Building building, float altitude) { + + if (!DbManager.BuildingQueries.UPDATE_BUILDING_ALTITUDE(building.getObjectUUID(), altitude)) + return false; + + building.statAlt = altitude; + + if (building.parentZone != null) { + if (building.parentBuildingID != 0) { + Building parentBuilding = BuildingManager.getBuilding(building.parentBuildingID); + if (parentBuilding != null) { + building.setLoc(new Vector3fImmutable(building.statLat + building.parentZone.absX + parentBuilding.statLat, building.statAlt + building.parentZone.absY + parentBuilding.statAlt, building.statLon + building.parentZone.absZ + parentBuilding.statLon)); + } else { + building.setLoc(new Vector3fImmutable(building.statLat + building.parentZone.absX, building.statAlt + building.parentZone.absY, building.statLon + building.parentZone.absZ)); + + } + } else + building.setLoc(new Vector3fImmutable(building.statLat + building.parentZone.absX, building.statAlt + building.parentZone.absY, building.statLon + building.parentZone.absZ)); + + } + + return true; + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + if (target.getObjectType() != GameObjectType.Building) { + this.sendUsage(pc); + return; + } + + Building targetBuilding = (Building) target; + + + float altitude = 0; + try { + altitude = Float.parseFloat(words[0]); + + if (!UpdateBuildingAltitude(targetBuilding, targetBuilding.getStatAlt() + altitude)) { + this.throwbackError(pc, "Failed to update building altitude"); + return; + } + + + WorldGrid.updateObject(targetBuilding); + + this.setTarget(pc); //for logging + + // Update all surrounding clients. + + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied data: " + words[0] + + " failed to parse to an Integer."); + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to setSlot to " + + words[0]); + } + } + + @Override + protected String _getHelpString() { + return "Sets slot position for an NPC to 'slot'"; + } + + @Override + protected String _getUsageString() { + return "' /changeslot slot'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetForceRenameCityCmd.java b/src/engine/devcmd/cmds/SetForceRenameCityCmd.java new file mode 100644 index 00000000..3fded48b --- /dev/null +++ b/src/engine/devcmd/cmds/SetForceRenameCityCmd.java @@ -0,0 +1,53 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.AbstractGameObject; +import engine.objects.City; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + + +public class SetForceRenameCityCmd extends AbstractDevCmd { + + public SetForceRenameCityCmd() { + super("forcerename"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + if (zone == null) + return; + boolean rename = words[0].equalsIgnoreCase("true") ? true : false; + if (zone.getPlayerCityUUID() == 0) + return; + City city = City.getCity(zone.getPlayerCityUUID()); + if (city == null) + return; + city.setForceRename(rename); + + } + + @Override + protected String _getHelpString() { + return "Temporarily Changes SubRace"; + } + + @Override + protected String _getUsageString() { + return "' /subrace mobBaseID"; + } + +} diff --git a/src/engine/devcmd/cmds/SetGuildCmd.java b/src/engine/devcmd/cmds/SetGuildCmd.java new file mode 100644 index 00000000..d869081b --- /dev/null +++ b/src/engine/devcmd/cmds/SetGuildCmd.java @@ -0,0 +1,103 @@ +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.Guild; +import engine.objects.NPC; +import engine.objects.PlayerCharacter; + +/** + * @author Dev command to set the guild of targeted npc. + * Argument is a valid guild UID + */ +public class SetGuildCmd extends AbstractDevCmd { + + + public SetGuildCmd() { + super("setguild"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + + NPC targetNPC; + Guild targetGuild; + + if (validateUserInput(pcSender, target, args) == false) { + this.sendUsage(pcSender); + return; + } + + // Valid arguments, attempt to set guild of NPC. + + targetNPC = getTargetAsNPC(pcSender); + targetGuild = Guild.getGuild(Integer.parseInt(args[0])); + + DbManager.NPCQueries.SET_PROPERTY(targetNPC, "npc_guildID", args[0]); + targetNPC.setGuild(targetGuild); + + // Refresh loaded game object + + WorldGrid.updateObject(targetNPC, pcSender); + + } + + @Override + protected String _getUsageString() { + return "' /setguild [UID]"; + } + + @Override + protected String _getHelpString() { + return "Assigns NPC to a given guild"; + } + + private boolean validateUserInput(PlayerCharacter pcSender, AbstractGameObject currTarget, String[] userInput) { + + Guild tempGuild; + + // Incorrect number of arguments + + if (userInput.length != 1) + return false; + + // No target + + if (currTarget == null) { + throwbackError(pcSender, "Requires an NPC be targeted"); + return false; + } + + // Target must be an NPC + + if (currTarget.getObjectType() != GameObjectType.NPC) { + throwbackError(pcSender, "Invalid object. Must be an NPC"); + return false; + } + + // Argument must parse as a int. + + try { + Integer.parseInt(userInput[0]); + } catch (NumberFormatException | NullPointerException e) { + return false; + } + + // Argument must return a valid guild + + tempGuild = Guild.getGuild(Integer.parseInt(userInput[0])); + + if (tempGuild == null) { + throwbackError(pcSender, "Invalid Guild UID"); + return false; + } + + + return true; + } + +} diff --git a/src/engine/devcmd/cmds/SetHealthCmd.java b/src/engine/devcmd/cmds/SetHealthCmd.java new file mode 100644 index 00000000..3ee2763b --- /dev/null +++ b/src/engine/devcmd/cmds/SetHealthCmd.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.DispatchChannel; +import engine.devcmd.AbstractDevCmd; +import engine.net.DispatchMessage; +import engine.net.client.msg.TargetedActionMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + +public class SetHealthCmd extends AbstractDevCmd { + + public SetHealthCmd() { + super("setHealth"); + this.addCmdString("health"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + float amount = 0.0f; + try { + amount = Float.parseFloat(words[0]); + pc.modifyHealth(amount, pc, false); + this.setTarget(pc); //for logging + + // Update all surrounding clients. + TargetedActionMsg cmm = new TargetedActionMsg(pc); + DispatchMessage.dispatchMsgToInterestArea(pc, cmm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied data: " + words[0] + + " failed to parse to a Float."); + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to setHealth to " + + words[0]); + } + } + + @Override + protected String _getHelpString() { + return "Sets your character's health to 'amount'"; + } + + @Override + protected String _getUsageString() { + return "' /setHealth amount'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetInvulCmd.java b/src/engine/devcmd/cmds/SetInvulCmd.java new file mode 100644 index 00000000..f26df9e4 --- /dev/null +++ b/src/engine/devcmd/cmds/SetInvulCmd.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + + +import engine.Enum; +import engine.Enum.ProtectionState; +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.PlayerCharacter; + +public class SetInvulCmd extends AbstractDevCmd { + + public SetInvulCmd() { + super("setinvul"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] words, + AbstractGameObject target) { + if (pcSender == null) + return; + + if (words.length != 1) { + this.sendUsage(pcSender); + return; + } + + boolean invul; + switch (words[0].toLowerCase()) { + case "true": + invul = true; + break; + case "false": + invul = false; + break; + default: + this.sendUsage(pcSender); + return; + } + + if (target == null || !(target instanceof Building)) { + throwbackError(pcSender, "No building targeted"); + return; + } + + Building b = (Building) target; + + // if strucutre is a TOL then we're modifying the protection + // status of the entire city + + if ((b.getBlueprint() != null) && + (b.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.TOL))) { + + City city; + + city = b.getCity(); + city.protectionEnforced = !city.protectionEnforced; + throwbackInfo(pcSender, "City protection contracts enforced: " + city.protectionEnforced); + return; + } + + if (invul) { + + b.setProtectionState(ProtectionState.PROTECTED); + throwbackInfo(pcSender, "The targetted building is now invulnerable."); + return; + } + b.setProtectionState(ProtectionState.NONE); + throwbackInfo(pcSender, "The targetted building is no longer invulernable."); + } + + @Override + protected String _getUsageString() { + return "'./setInvul true|false'"; + } + + @Override + protected String _getHelpString() { + return "Turns invulernability on or off for building"; + } + +} diff --git a/src/engine/devcmd/cmds/SetLevelCmd.java b/src/engine/devcmd/cmds/SetLevelCmd.java new file mode 100644 index 00000000..47be4ff6 --- /dev/null +++ b/src/engine/devcmd/cmds/SetLevelCmd.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.InterestManager; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class SetLevelCmd extends AbstractDevCmd { + + public SetLevelCmd() { + super("setLevel"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + PlayerCharacter tar; + if (target != null) { + if (target instanceof PlayerCharacter) + tar = (PlayerCharacter) target; + else + tar = pc; + } else + tar = pc; + + int level = 0; + try { + level = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + this.sendUsage(pc); + return; + } + if (level < 1 || level > 75) { + this.sendHelp(pc); + return; + } + + if (level > 10 && pc.getPromotionClass() == null) + level = 10; + + tar.setLevel((short) level); + this.setTarget(tar); //for logging + ChatManager.chatSayInfo(pc, tar.getFirstName() + " level changed to " + level); + InterestManager.reloadCharacter(tar); + } + + @Override + protected String _getHelpString() { + return "Sets your character's level to 'amount'. 'amount' must be between 1-75"; + } + + @Override + protected String _getUsageString() { + return "' /setLevel amount'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetMaintCmd.java b/src/engine/devcmd/cmds/SetMaintCmd.java new file mode 100644 index 00000000..85346322 --- /dev/null +++ b/src/engine/devcmd/cmds/SetMaintCmd.java @@ -0,0 +1,60 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.MaintenanceManager; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +import java.time.LocalDateTime; + +public class SetMaintCmd extends AbstractDevCmd { + + public SetMaintCmd() { + super("setMaint"); + this.addCmdString("setmaint"); + } + + @Override + protected void _doCmd(PlayerCharacter player, String[] words, + AbstractGameObject target) { + + if (!target.getObjectType().equals(Enum.GameObjectType.Building)) { + ChatManager.chatSayInfo(player, "Target is not a valid building"); + return; + } + + Building targetBuilding = (Building) target; + + if (targetBuilding.getProtectionState().equals(Enum.ProtectionState.NPC)) { + ChatManager.chatSayInfo(player, "Target is not a valid building"); + return; + } + + MaintenanceManager.setMaintDateTime(targetBuilding, LocalDateTime.now().minusDays(1).withHour(13).withMinute(0).withSecond(0).withNano(0)); + ChatManager.chatSayInfo(player, "Maint will run for UUID: " + targetBuilding.getObjectUUID()); + } + + + @Override + protected String _getHelpString() { + return "Sets the Rank of either the targets object or the object specified by ID."; + } + + @Override + protected String _getUsageString() { + return "' /setrank ID rank' || ' /setrank rank' || ' /rank ID rank' || ' /rank rank'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetManaCmd.java b/src/engine/devcmd/cmds/SetManaCmd.java new file mode 100644 index 00000000..3988e2a3 --- /dev/null +++ b/src/engine/devcmd/cmds/SetManaCmd.java @@ -0,0 +1,56 @@ +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.net.DispatchMessage; +import engine.net.client.msg.TargetedActionMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + +public class SetManaCmd extends AbstractDevCmd { + + public SetManaCmd() { + super("setMana"); + this.addCmdString("mana"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + float amount = 0.0f; + try { + amount = Float.parseFloat(words[0]); + pc.setMana(amount, pc); + this.setTarget(pc); //for logging + + //Update all surrounding clients. - NOT for Mana? + TargetedActionMsg cmm = new TargetedActionMsg(pc); + DispatchMessage.dispatchMsgToInterestArea(pc, cmm, engine.Enum.DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied data: " + words[0] + + " failed to parse to a Float."); + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to setMana to " + + words[0]); + } + } + + @Override + protected String _getHelpString() { + return "Sets your character's Mana to 'amount'"; + } + + @Override + protected String _getUsageString() { + return "' /setMana amount'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetMineExpansion.java b/src/engine/devcmd/cmds/SetMineExpansion.java new file mode 100644 index 00000000..192329d5 --- /dev/null +++ b/src/engine/devcmd/cmds/SetMineExpansion.java @@ -0,0 +1,80 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.Mine; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class SetMineExpansion extends AbstractDevCmd { + + public SetMineExpansion() { + super("setexpansion"); + this.addCmdString("setexpansion"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + + + if (target.getObjectType() != GameObjectType.Building) + return; + Building mineBuilding = BuildingManager.getBuilding(target.getObjectUUID()); + if (mineBuilding == null) + return; + Mine mine = Mine.getMineFromTower(mineBuilding.getObjectUUID()); + if (mine == null) + return; + int bit = 2; + switch (args[0].toUpperCase()) { + case "ON": + + bit |= mine.getFlags(); + if (!DbManager.MineQueries.SET_FLAGS(mine, bit)) + return; + mine.setFlags(bit); + ChatManager.chatSystemInfo(pcSender, mine.getZoneName() + "'s " + mine.getMineType().name + " is now an expansion mine."); + break; + + case "OFF": + bit &= ~mine.getFlags(); + if (!DbManager.MineQueries.SET_FLAGS(mine, bit)) + return; + mine.setFlags(bit); + ChatManager.chatSystemInfo(pcSender, mine.getZoneName() + "'s " + mine.getMineType().name + " is no longer an expansion mine."); + break; + + } + + + } + + @Override + protected String _getUsageString() { + return "' /setmineexpansion on|off"; + } + + @Override + protected String _getHelpString() { + return "enables or disables an expansion type for a mine."; + } + +} diff --git a/src/engine/devcmd/cmds/SetMineTypeCmd.java b/src/engine/devcmd/cmds/SetMineTypeCmd.java new file mode 100644 index 00000000..a469fe07 --- /dev/null +++ b/src/engine/devcmd/cmds/SetMineTypeCmd.java @@ -0,0 +1,62 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.objects.*; + +/** + * @author Eighty + */ +public class SetMineTypeCmd extends AbstractDevCmd { + + public SetMineTypeCmd() { + super("setminetype"); + this.addCmdString("setminetype"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + + MineProduction mineType = MineProduction.valueOf(args[0].toUpperCase()); + if (mineType == null) + return; + + if (target.getObjectType() != GameObjectType.Building) + return; + Building mineBuilding = BuildingManager.getBuilding(target.getObjectUUID()); + if (mineBuilding == null) + return; + Mine mine = Mine.getMineFromTower(mineBuilding.getObjectUUID()); + if (mine == null) + return; + if (!DbManager.MineQueries.CHANGE_TYPE(mine, mineType)) + return; + mine.setMineType(mineType.name()); + ChatManager.chatSystemInfo(pcSender, "The mine in " + mine.getZoneName() + " is now a(n) " + mine.getMineType().name); + } + + @Override + protected String _getUsageString() { + return "' /setminetype gold|ore|magic|lumber"; + } + + @Override + protected String _getHelpString() { + return "Changes the mine type of the current mine."; + } + +} diff --git a/src/engine/devcmd/cmds/SetNpcEquipSetCmd.java b/src/engine/devcmd/cmds/SetNpcEquipSetCmd.java new file mode 100644 index 00000000..844d6613 --- /dev/null +++ b/src/engine/devcmd/cmds/SetNpcEquipSetCmd.java @@ -0,0 +1,136 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.MobBase; +import engine.objects.NPC; +import engine.objects.PlayerCharacter; + +public class SetNpcEquipSetCmd extends AbstractDevCmd { + + public static int lastEquipSetID = 0; + + public SetNpcEquipSetCmd() { + super("setEquipSet"); + this.addCmdString("equipset"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + if (target.getObjectType() != GameObjectType.NPC) { + this.sendUsage(pc); + return; + } + + NPC npc = (NPC) target; + + if (words[0].equalsIgnoreCase("next")) { + + if (SetNpcEquipSetCmd.lastEquipSetID >= 1222) + SetNpcEquipSetCmd.lastEquipSetID = 1; + else + SetNpcEquipSetCmd.lastEquipSetID++; + + boolean complete = false; + + while (complete == false) { + complete = NPC.UpdateEquipSetID(npc, SetNpcEquipSetCmd.lastEquipSetID); + + if (!complete) { + SetNpcEquipSetCmd.lastEquipSetID++; + if (SetNpcEquipSetCmd.lastEquipSetID >= 1222) + SetNpcEquipSetCmd.lastEquipSetID = 1; + } + } + + if (complete) { + npc.equip = MobBase.loadEquipmentSet(npc.getEquipmentSetID()); + WorldGrid.updateObject(npc); + this.throwbackInfo(pc, "Equipment Set Changed to " + SetNpcEquipSetCmd.lastEquipSetID); + } + + + return; + } + + if (words[0].equalsIgnoreCase("last")) { + + if (SetNpcEquipSetCmd.lastEquipSetID <= 1) + SetNpcEquipSetCmd.lastEquipSetID = 1222; + else + SetNpcEquipSetCmd.lastEquipSetID--; + + boolean complete = false; + + while (complete == false) { + complete = NPC.UpdateEquipSetID(npc, SetNpcEquipSetCmd.lastEquipSetID); + + if (!complete) { + SetNpcEquipSetCmd.lastEquipSetID--; + if (SetNpcEquipSetCmd.lastEquipSetID <= 1) + SetNpcEquipSetCmd.lastEquipSetID = 1222; + } + + } + + if (complete) { + this.throwbackInfo(pc, "Equipment Set Changed to " + SetNpcEquipSetCmd.lastEquipSetID); + npc.equip = MobBase.loadEquipmentSet(npc.getEquipmentSetID()); + WorldGrid.updateObject(npc); + } + + + return; + } + + int equipSetID = 0; + + try { + equipSetID = Integer.parseInt(words[0]); + } catch (Exception e) { + this.throwbackError(pc, e.getMessage()); + } + + if (!NPC.UpdateEquipSetID(npc, equipSetID)) { + this.throwbackError(pc, "Unable to find Equipset for ID " + equipSetID); + return; + } + + SetNpcEquipSetCmd.lastEquipSetID = equipSetID; + npc.equip = MobBase.loadEquipmentSet(npc.getEquipmentSetID()); + WorldGrid.updateObject(npc); + + this.throwbackInfo(pc, "Equipment Set Changed to " + equipSetID); + + + } + + @Override + protected String _getHelpString() { + return "Sets slot position for an NPC to 'slot'"; + } + + @Override + protected String _getUsageString() { + return "' /changeslot slot'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetNpcMobbaseCmd.java b/src/engine/devcmd/cmds/SetNpcMobbaseCmd.java new file mode 100644 index 00000000..4b5c708f --- /dev/null +++ b/src/engine/devcmd/cmds/SetNpcMobbaseCmd.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.MobBase; +import engine.objects.NPC; +import engine.objects.PlayerCharacter; + +public class SetNpcMobbaseCmd extends AbstractDevCmd { + + public SetNpcMobbaseCmd() { + super("setmobbase"); + this.addCmdString("npcmobbase"); + } + + @Override + protected void _doCmd(PlayerCharacter player, String[] words, + AbstractGameObject target) { + + // Arg Count Check + if (words.length != 1) { + this.sendUsage(player); + return; + } + + if (target.getObjectType() != GameObjectType.NPC) { + this.sendUsage(player); + return; + } + + NPC npc = (NPC) target; + + int mobBaseID = Integer.parseInt(words[0]); + + if (MobBase.getMobBase(mobBaseID) == null) { + this.throwbackError(player, "Cannot find Mobbase for ID " + mobBaseID); + return; + } + NPC.UpdateRaceID(npc, mobBaseID); + + WorldGrid.updateObject(npc); + + } + + @Override + protected String _getHelpString() { + return "Sets mobbase override for an NPC"; + } + + @Override + protected String _getUsageString() { + return "' /setmobbase mobBaseID'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetNpcNameCmd.java b/src/engine/devcmd/cmds/SetNpcNameCmd.java new file mode 100644 index 00000000..d670db67 --- /dev/null +++ b/src/engine/devcmd/cmds/SetNpcNameCmd.java @@ -0,0 +1,62 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.NPC; +import engine.objects.PlayerCharacter; + +public class SetNpcNameCmd extends AbstractDevCmd { + + public static int lastEquipSetID = 0; + + public SetNpcNameCmd() { + super("setNPCName"); + this.addCmdString("npcname"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + if (target.getObjectType() != GameObjectType.NPC) { + this.sendUsage(pc); + return; + } + + NPC npc = (NPC) target; + + String name = words[0]; + + NPC.UpdateName(npc, name); + + WorldGrid.updateObject(npc); + + } + + @Override + protected String _getHelpString() { + return "Sets slot position for an NPC to 'slot'"; + } + + @Override + protected String _getUsageString() { + return "' /changeslot slot'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetOwnerCmd.java b/src/engine/devcmd/cmds/SetOwnerCmd.java new file mode 100644 index 00000000..f7753db2 --- /dev/null +++ b/src/engine/devcmd/cmds/SetOwnerCmd.java @@ -0,0 +1,117 @@ +package engine.devcmd.cmds; + +import engine.Enum.DbObjectType; +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.DbManager; +import engine.objects.*; + + +/** + * @author Dev command to set the owner of targeted building. + * Argument is a valid guild UID + */ +public class SetOwnerCmd extends AbstractDevCmd { + + Building _targetBuilding = null; + DbObjectType _newOwnerType; + AbstractCharacter outOwner; + + public SetOwnerCmd() { + super("setowner"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + + if (validateUserInput(pcSender, target, args) == false) { + this.sendUsage(pcSender); + return; + } + + // Valid arguments, attempt to set owner of Building. + + _targetBuilding = getTargetAsBuilding(pcSender); + + // if it's a tol change ownership of the city + + if (_targetBuilding.getBlueprint() != null && + _targetBuilding.getBlueprint().getBuildingGroup().equals(engine.Enum.BuildingGroup.TOL)) { + + City city = _targetBuilding.getCity(); + + if (city != null) { + city.claim(outOwner); + return; + } + } + _targetBuilding.setOwner(outOwner); + + DbManager.BuildingQueries.SET_PROPERTY(_targetBuilding, "ownerUUID", args[0]); + + _targetBuilding.refreshGuild(); + + } + + @Override + protected String _getUsageString() { + return "' /setowner [UID]"; + } + + @Override + protected String _getHelpString() { + return "Assigns new owner to building"; + } + + private boolean validateUserInput(PlayerCharacter pcSender, AbstractGameObject currTarget, String[] userInput) { + + // Incorrect number of arguments + + if (userInput.length != 1) + return false; + + // No target + + if (currTarget == null) { + throwbackError(pcSender, "Requires a Building to be targeted"); + return false; + } + + // Target must be an Building + + if (currTarget.getObjectType() != GameObjectType.Building) { + throwbackError(pcSender, "Invalid target. Must be a Building"); + return false; + } + + // Argument must parse to a long. + + try { + Long.parseLong(userInput[0]); + } catch (NumberFormatException | NullPointerException e) { + return false; + } + + // Argument must return a valid NPC or PlayerCharacter + + _newOwnerType = DbManager.BuildingQueries.GET_UID_ENUM(Long.parseLong(userInput[0])); + + switch (_newOwnerType) { + case NPC: + outOwner = (AbstractCharacter) DbManager.getObject(GameObjectType.NPC, Integer.parseInt(userInput[0])); + break; + case CHARACTER: + outOwner = (AbstractCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, Integer.parseInt(userInput[0])); + break; + } + + if (outOwner == null) { + throwbackError(pcSender, "Invalid Owner UID"); + return false; + } + + return true; + } + +} diff --git a/src/engine/devcmd/cmds/SetPromotionClassCmd.java b/src/engine/devcmd/cmds/SetPromotionClassCmd.java new file mode 100644 index 00000000..f8776786 --- /dev/null +++ b/src/engine/devcmd/cmds/SetPromotionClassCmd.java @@ -0,0 +1,72 @@ +package engine.devcmd.cmds; + +import engine.Enum.DispatchChannel; +import engine.InterestManagement.InterestManager; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.net.DispatchMessage; +import engine.net.client.msg.ApplyRuneMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.PromotionClass; +import engine.server.MBServerStatics; + +public class SetPromotionClassCmd extends AbstractDevCmd { + + public SetPromotionClassCmd() { + super("setPromotionClass"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + int classID = 0; + try { + classID = Integer.parseInt(words[0]); + } catch (Exception e) { + throwbackError(pc, + "Invalid setPromotionClass Command. must specify an ID between 2504 and 2526."); + return; + } + if (classID < 2504 || classID > 2526 || classID == 2522) { + throwbackError(pc, + "Invalid setPromotionClass Command. must specify an ID between 2504 and 2526."); + return; + } + pc.setPromotionClass(classID); + ChatManager.chatSayInfo(pc, + "PromotionClass changed to " + classID); + InterestManager.reloadCharacter(pc); + this.setTarget(pc); //for logging + + + // recalculate all bonuses/formulas/skills/powers + pc.recalculate(); + + // send the rune application to the clients + + PromotionClass promo = pc.getPromotionClass(); + if (promo != null) { + ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), promo.getObjectUUID(), promo.getObjectType().ordinal(), promo.getObjectUUID(), true); + DispatchMessage.dispatchMsgToInterestArea(pc, arm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + + } + + @Override + protected String _getHelpString() { + return "Sets your character's PromotionClass to 'ID'"; + } + + @Override + protected String _getUsageString() { + return "' /setPromotionClass id'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetRankCmd.java b/src/engine/devcmd/cmds/SetRankCmd.java new file mode 100644 index 00000000..614701c4 --- /dev/null +++ b/src/engine/devcmd/cmds/SetRankCmd.java @@ -0,0 +1,140 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.WorldGrid; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.objects.*; + +public class SetRankCmd extends AbstractDevCmd { + + public SetRankCmd() { + super("setRank"); + this.addCmdString("setrank"); + this.addCmdString("rank"); + } + + @Override + protected void _doCmd(PlayerCharacter player, String[] words, + AbstractGameObject target) { + + int targetRank; + int uuid = 0; + + if (words.length == 2) { + try { + uuid = Integer.parseInt(words[0]); + targetRank = Integer.parseInt(words[1]); + } catch (NumberFormatException e) { + this.sendUsage(player); + return; // NaN + } + } else if (words.length == 1) { + try { + targetRank = Integer.parseInt(words[0]); + } catch (NumberFormatException e) { + this.sendUsage(player); + return; // NaN + } + } else { + this.sendUsage(player); + return; + } + + if (target != null) { + switch (target.getObjectType()) { + case Building: + Building targetBuilding = (Building) target; + Blueprint blueprint = targetBuilding.getBlueprint(); + + if (blueprint == null) { + targetBuilding.setRank(targetRank); + ChatManager.chatSayInfo(player, "Building ranked without blueprint" + targetBuilding.getObjectUUID()); + return; + } + + if (targetRank > blueprint.getMaxRank()) { + throwbackError(player, "Attempt to set Invalid Rank" + targetBuilding.getObjectUUID()); + return; + } + + // Set the current targetRank + int lastMeshID = targetBuilding.getMeshUUID(); + targetBuilding.setRank(targetRank); + + ChatManager.chatSayInfo(player, "Rank set for building with ID " + targetBuilding.getObjectUUID() + " to rank " + targetRank); + break; + case NPC: + NPC toRank = (NPC) target; + toRank.setRank(targetRank * 10); + toRank.setUpgradeDateTime(null); + WorldGrid.updateObject(toRank); + break; + case Mob: + Mob toRankCaptain = (Mob) target; + if (toRankCaptain.getContract() != null) { + toRankCaptain.setRank(targetRank * 10); + Mob.setUpgradeDateTime(toRankCaptain, null); + WorldGrid.updateObject(toRankCaptain); + } + + break; + } + + } else { + Building targetBuilding = null; + if (uuid != 0) + targetBuilding = BuildingManager.getBuilding(uuid); + + if (targetBuilding == null) { + throwbackError(player, "Unable to find building."); + return; + } + + Blueprint blueprint = targetBuilding.getBlueprint(); + + if (blueprint == null) { + throwbackError(player, "Attempt to rank building without blueprint" + targetBuilding.getObjectUUID()); + return; + } + + if (targetRank > blueprint.getMaxRank()) { + throwbackError(player, "Attempt to set Invalid Rank" + targetBuilding.getObjectUUID()); + return; + } + + // Set the current targetRank + int lastMeshID = targetBuilding.getMeshUUID(); + targetBuilding.setRank(targetRank); + + if (lastMeshID != targetBuilding.getMeshUUID()) + targetBuilding.refresh(true); + else + targetBuilding.refresh(false); + + ChatManager.chatSayInfo(player, "Rank set for building with ID " + targetBuilding.getObjectUUID() + " to rank " + targetRank); + } + + + } + + @Override + protected String _getHelpString() { + return "Sets the Rank of either the targets object or the object specified by ID."; + } + + @Override + protected String _getUsageString() { + return "' /setrank ID rank' || ' /setrank rank' || ' /rank ID rank' || ' /rank rank'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetRuneCmd.java b/src/engine/devcmd/cmds/SetRuneCmd.java new file mode 100644 index 00000000..1379ab35 --- /dev/null +++ b/src/engine/devcmd/cmds/SetRuneCmd.java @@ -0,0 +1,78 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.InterestManagement.InterestManager; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.CharacterRune; +import engine.objects.PlayerCharacter; + +/** + * @author Eighty + */ +public class SetRuneCmd extends AbstractDevCmd { + + public SetRuneCmd() { + super("setRune"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + int runeID = 0; + boolean add = true; + try { + runeID = Integer.parseInt(args[0]); + if (args.length > 1) + add = (args[1].toLowerCase().equals("false")) ? false : true; + } catch (NumberFormatException e) { + this.sendUsage(pcSender); + return; + } + if (runeID < 3001 || runeID > 3049) { + throwbackError(pcSender, + "Invalid setrune Command. must specify an ID between 3001 and 3048."); + return; + } + boolean worked = false; + if (add) { + worked = CharacterRune.grantRune(pcSender, runeID); + if (worked) + ChatManager.chatSayInfo(pcSender, + "rune of ID " + runeID + " added"); + else + throwbackError(pcSender, "Failed to add the rune of type " + + runeID); + } else { + worked = CharacterRune.removeRune(pcSender, runeID); + if (worked) { + ChatManager.chatSayInfo(pcSender, + "rune of ID " + runeID + " removed"); + InterestManager.reloadCharacter(pcSender); + } else + throwbackError(pcSender, "Failed to remove the rune of type " + + runeID); + } + this.setTarget(pcSender); //for logging + } + + @Override + protected String _getUsageString() { + return "' /setrune runeID [true/false]'"; + } + + @Override + protected String _getHelpString() { + return "Grant or remove the rune with the specified runeID"; + } + +} diff --git a/src/engine/devcmd/cmds/SetStaminaCmd.java b/src/engine/devcmd/cmds/SetStaminaCmd.java new file mode 100644 index 00000000..d760a1cc --- /dev/null +++ b/src/engine/devcmd/cmds/SetStaminaCmd.java @@ -0,0 +1,67 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.DispatchChannel; +import engine.devcmd.AbstractDevCmd; +import engine.net.DispatchMessage; +import engine.net.client.msg.TargetedActionMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + + +public class SetStaminaCmd extends AbstractDevCmd { + + public SetStaminaCmd() { + super("setStamina"); + this.addCmdString("stamina"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + // Arg Count Check + if (words.length != 1) { + this.sendUsage(pc); + return; + } + + float amount = 0.0f; + try { + amount = Float.parseFloat(words[0]); + pc.setStamina(amount, pc); + this.setTarget(pc); //for logging + + // Update all surrounding clients. + TargetedActionMsg cmm = new TargetedActionMsg(pc); + DispatchMessage.dispatchMsgToInterestArea(pc, cmm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied data: " + words[0] + + " failed to parse to a Float."); + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to setStamina to " + + words[0]); + } + } + + @Override + protected String _getHelpString() { + return "Sets your character's Stamina to 'amount'"; + } + + @Override + protected String _getUsageString() { + return "' /setStamina amount'"; + } + +} diff --git a/src/engine/devcmd/cmds/SetSubRaceCmd.java b/src/engine/devcmd/cmds/SetSubRaceCmd.java new file mode 100644 index 00000000..4ad42ffc --- /dev/null +++ b/src/engine/devcmd/cmds/SetSubRaceCmd.java @@ -0,0 +1,196 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.net.DispatchMessage; +import engine.net.client.msg.ApplyBuildingEffectMsg; +import engine.net.client.msg.UpdateCharOrMobMessage; +import engine.objects.*; + +public class SetSubRaceCmd extends AbstractDevCmd { + + public SetSubRaceCmd() { + super("setSubRace"); + this.addCmdString("subrace"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + if (target instanceof AbstractCharacter) { + + if (words[0].equals("race")) { + if (target.getObjectType() != GameObjectType.PlayerCharacter) + return; + PlayerCharacter player = (PlayerCharacter) target; + int raceID = Integer.parseInt(words[1]); + player.setSubRaceID(raceID); + if (raceID == 0) + raceID = player.getRaceID(); + UpdateCharOrMobMessage ucm = new UpdateCharOrMobMessage(player, 1, raceID); + DispatchMessage.sendToAllInRange(player, ucm); + return; + } + if (words[0].equals("all")) { + for (int i = 15999; i < 16337; i++) { + ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(4, 0, target.getObjectType().ordinal(), target.getObjectUUID(), i); + DispatchMessage.sendToAllInRange((AbstractWorldObject) target, applyBuildingEffectMsg); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } else { + ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(4, 0, target.getObjectType().ordinal(), target.getObjectUUID(), Integer.parseInt(words[0])); + DispatchMessage.sendToAllInRange((AbstractWorldObject) target, applyBuildingEffectMsg); + } + + return; + } + + Building building = (Building) target; + + building.removeAllVisualEffects(); + building.addEffectBit(1 << Integer.parseInt(words[0])); + building.updateEffects(); + //63535 38751 + // Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + // //CityZoneMsg czm = new CityZoneMsg(3, zone.getLoc().x, zone.getLoc().y, zone.getLoc().z, "balls", zone, 0, 0); + // pc.getClientConnection().sendMsg(new DeleteItemMsg(zone.getObjectType().ordinal(), zone.getObjectUUID())); + + // UpdateInventoryMsg updateInventoryMsg = new UpdateInventoryMsg(new ArrayList<>(), new ArrayList<>(), null, true); + // pc.getClientConnection().sendMsg(updateInventoryMsg); + + //pc.getCharItemManager().updateInventory(); + + + // Mob mob = (Mob)target; + // + // if (mob == null) + // return; + // + // MobLoot mobLoot = new MobLoot(mob, ItemBase.getItemBase(Integer.parseInt(words[0])), false); + // + // mob.getCharItemManager().addItemToInventory(mobLoot); + + + // if (target.getObjectType() != GameObjectType.Building) + // return; + // + // Building warehouseBuilding = (Building)target; + // + // if (Warehouse.warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID()) == null) + // return; + // + // Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID()); + // + // for (int ibID: Warehouse.getMaxResources().keySet()){ + // ItemBase ib = ItemBase.getItemBase(ibID); + // warehouse.depositFromMine(null, ib, Warehouse.getMaxResources().get(ibID)); + // } + + + // int raceID = Integer.parseInt(words[0]); + // + // UpdateCharOrMobMessage ucm = new UpdateCharOrMobMessage(pc, raceID); + // + // pc.getClientConnection().sendMsg(ucm); + + // LoadCharacterMsg lcm = new LoadCharacterMsg((AbstractCharacter)null,false); + // try { + // DispatchMessage.sendToAllInRange(pc, lcm); + // } catch (MsgSendException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + // ModifyHealthMsg mhm =new ModifyHealthMsg(pc, pc, -50f, 0f, 0f, 0, null, 9999, 0); + // mhm.setOmitFromChat(1); + // pc.getClientConnection().sendMsg(mhm); + // + // int temp = 0; + // boolean start = false; + // + // for (EffectsBase eb: EffectsBase.getAllEffectsBase()){ + // + // + // + // if (!pc.getClientConnection().isConnected()){ + // Logger.info("", "PLAYER DC!" + eb.getIDString()); + // break; + // } + // + // eb = PowersManager.getEffectByIDString("WLR-018A"); + // + // + // NoTimeJob ntj = new NoTimeJob(pc, "NONE", eb, 40); + // pc.addEffect(String.valueOf(eb.getUUID()), 1000, ntj, eb, 40); + // eb.sendEffectNoPower(ntj, 1000, pc.getClientConnection()); + // + // ThreadUtils.sleep(500); + // pc.clearEffects(); + // + // //WorldServer.updateObject((AbstractWorldObject)target, pc); + // this.throwbackInfo(pc, eb.getIDString()); + // break; + // //ThreadUtils.sleep(500); + // + // } + + + // for (EffectsBase eb : EffectsBase.getAllEffectsBase()){ + // if (eb.getToken() == 0) + // continue; + // int token = eb.getToken(); + // ApplyEffectMsg pum = new ApplyEffectMsg(); + // pum.setEffectID(token); + // pum.setSourceType(pc.getObjectType().ordinal()); + // pum.setSourceID(pc.getObjectUUID()); + // pum.setTargetType(pc.getObjectType().ordinal()); + // pum.setTargetID(pc.getObjectUUID()); + // pum.setNumTrains(40); + // pum.setDuration(-1); + //// pum.setDuration((pb.isChant()) ? (int)pb.getChantDuration() : ab.getDurationInSeconds(trains)); + // pum.setPowerUsedID(0); + // // pum.setPowerUsedName("Inflict Poison"); + // this.throwbackInfo(pc, eb.getName() + "Token = " + eb.getToken()); + // pc.getClientConnection().sendMsg(pum); + // ThreadUtils.sleep(200); + // } + // + + // UpdateObjectMsg uom = new UpdateObjectMsg(pc, 4); + // try { + // Location.sendToAllInRange(pc, uom); + // } catch (MsgSendException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + + + } + + @Override + protected String _getHelpString() { + return "Temporarily Changes SubRace"; + } + + @Override + protected String _getUsageString() { + return "' /subrace mobBaseID"; + } + +} diff --git a/src/engine/devcmd/cmds/ShowOffsetCmd.java b/src/engine/devcmd/cmds/ShowOffsetCmd.java new file mode 100644 index 00000000..55b0a0fe --- /dev/null +++ b/src/engine/devcmd/cmds/ShowOffsetCmd.java @@ -0,0 +1,56 @@ +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +public class ShowOffsetCmd extends AbstractDevCmd { + + public ShowOffsetCmd() { + super("showoffset"); + } + + // AbstractDevCmd Overridden methods + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + + + Building targetBuilding; + String outString; + Vector3fImmutable offset; + + String newline = "\r\n "; + targetBuilding = (Building) target; + + if (targetBuilding.getObjectType() != GameObjectType.Building) { + throwbackInfo(pc, "showgate: target must be an Building"); + return; + } + + offset = pc.getLoc().subtract(targetBuilding.getLoc()); + + outString = "Location: " + pc.getLoc().x + "x " + pc.getLoc().z + 'y'; + outString += newline; + outString += "Offset: " + offset.x + "x " + offset.y + 'y'; + throwbackInfo(pc, outString); + } + + @Override + protected String _getHelpString() { + return "Shows offset to current building"; + } + + @Override + protected String _getUsageString() { + + + return "Shows offset to current building"; + } + + +} diff --git a/src/engine/devcmd/cmds/SimulateBootyCmd.java b/src/engine/devcmd/cmds/SimulateBootyCmd.java new file mode 100644 index 00000000..f6ef0bb0 --- /dev/null +++ b/src/engine/devcmd/cmds/SimulateBootyCmd.java @@ -0,0 +1,155 @@ +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.LootManager; +import engine.gameManager.ZoneManager; +import engine.loot.BootySetEntry; +import engine.objects.*; + +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; + +public class SimulateBootyCmd extends AbstractDevCmd { + public SimulateBootyCmd() { + super("bootysim"); + } + + @Override + protected void _doCmd(PlayerCharacter playerCharacter, String[] words, + AbstractGameObject target) { + + if (playerCharacter == null) + return; + + String newline = "\r\n "; + + String output; + + output = "Booty Simulation:" + newline; + + Mob mob = (Mob) target; + output += "Name: " + mob.getName() + newline; + output += "Special Loot:" + newline; + + if (mob.bootySet != 0) { + for (BootySetEntry entry : LootManager._bootySetMap.get(mob.bootySet)) { + ItemBase item = ItemBase.getItemBase(entry.itemBase); + if (item != null) { + output += "[" + entry.bootyType + "] " + item.getName() + " [Chance] " + entry.dropChance + newline; + } + } + } + + ArrayList GlassItems = new ArrayList(); + ArrayList Resources = new ArrayList(); + ArrayList Runes = new ArrayList(); + ArrayList Contracts = new ArrayList(); + ArrayList Offerings = new ArrayList(); + ArrayList OtherDrops = new ArrayList(); + ArrayList EquipmentDrops = new ArrayList(); + + int failures = 0; + int goldAmount = 0; + + for (int i = 0; i < 100; ++i) { + + try { + mob.loadInventory(); + for (Item lootItem : mob.getCharItemManager().getInventory()) { + switch (lootItem.getItemBase().getType()) { + case CONTRACT: //CONTRACT + Contracts.add(lootItem); + break; + case OFFERING: //OFFERING + Offerings.add(lootItem); + break; + case RESOURCE: //RESOURCE + Resources.add(lootItem); + break; + case RUNE: //RUNE + Runes.add(lootItem); + break; + case WEAPON: //WEAPON + if (lootItem.getItemBase().isGlass()) + GlassItems.add(lootItem); + else + OtherDrops.add(lootItem); + break; + case GOLD: + goldAmount += lootItem.getNumOfItems(); + break; + default: + OtherDrops.add(lootItem); + break; + } + } + } catch (Exception ex) { + failures++; + } + if (mob.getEquip() != null) { + for (MobEquipment me : mob.getEquip().values()) { + + if (me.getDropChance() == 0) + continue; + + float equipmentRoll = ThreadLocalRandom.current().nextInt(99) + 1; + float dropChance = me.getDropChance() * 100; + + if (equipmentRoll > (dropChance)) + continue; + + MobLoot ml = new MobLoot(mob, me.getItemBase(), false); + + if (ml != null) + EquipmentDrops.add(ml); + } + } + } + output += "MobBase BootySet: " + mob.getMobBase().bootySet + newline; + output += "Mob BootySet: " + mob.bootySet + newline; + output += "Tables Rolled On: " + newline; + + boolean hotZoneRan = false; + float dropRate = 1.0f; + + if (ZoneManager.inHotZone(mob.getLoc())) + dropRate = LootManager.HOTZONE_DROP_RATE; + else + dropRate = LootManager.NORMAL_DROP_RATE; + + for (BootySetEntry entry : LootManager._bootySetMap.get(mob.getMobBase().bootySet)) { + + if (entry.bootyType.equals("GOLD")) + output += "NORMAL TABLE [" + entry.bootyType + "] " + entry.genTable + ": " + entry.dropChance + newline; + else + output += "NORMAL TABLE [" + entry.bootyType + "] " + entry.genTable + ": " + entry.dropChance * dropRate + newline; + + if (hotZoneRan == false && ZoneManager.inHotZone(mob.getLoc()) && LootManager._genTables.containsKey(entry.genTable + 1)) { + output += "HOTZONE TABLE [" + entry.bootyType + "] " + (entry.genTable + 1) + ": " + entry.dropChance * dropRate + newline; + hotZoneRan = true; + } + } + + output += "GLASS DROPS: " + GlassItems.size() + newline; + output += "RUNE DROPS: " + Runes.size() + newline; + output += "CONTRACTS DROPS: " + Contracts.size() + newline; + output += "RESOURCE DROPS: " + Resources.size() + newline; + output += "OFFERINGS DROPPED: " + Offerings.size() + newline; + output += "ENCHANTED ITEMS DROPPED: " + OtherDrops.size() + newline; + output += "TOTAL GOLD DROPPED: " + goldAmount + newline; + output += "EQUIPMENT DROPPED: " + EquipmentDrops.size() + newline; + output += "FAILED ROLLS: " + failures + newline; + + throwbackInfo(playerCharacter, output); + } + + @Override + protected String _getHelpString() { + return "Simulates loot drops"; + } + + @Override + protected String _getUsageString() { + return "'/bootysim targetID'"; + } +} diff --git a/src/engine/devcmd/cmds/SlotNpcCmd.java b/src/engine/devcmd/cmds/SlotNpcCmd.java new file mode 100644 index 00000000..dcf2631e --- /dev/null +++ b/src/engine/devcmd/cmds/SlotNpcCmd.java @@ -0,0 +1,159 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.BuildingGroup; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.objects.*; +import engine.util.StringUtils; +import org.pmw.tinylog.Logger; + +/** + * Summary: Game designer utility command to add or + * remove building slot access for contracts + */ + +public class SlotNpcCmd extends AbstractDevCmd { + + public SlotNpcCmd() { + super("slotnpc"); + } + + + // AbstractDevCmd Overridden methods + + private static boolean validateUserInput(String[] userInput) { + + int stringIndex; + BuildingGroup testGroup; + + testGroup = BuildingGroup.FORGE; + + String commandSet = "onoff"; + + // incorrect number of arguments test + + if (userInput.length > 2) + return false; + + + // Test of toggle argument + + stringIndex = commandSet.indexOf(userInput[1].toLowerCase()); + + if (stringIndex == -1) + return false; + + // Validate we have a corrent building group name + + for (BuildingGroup group : BuildingGroup.values()) { + if (group.name().equals(userInput[0].toUpperCase())) + return true; + } + return false; + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + + Contract contract; + BuildingGroup buildingGroup; + NPC npc; + Mob mob; + + String outString; + + switch (target.getObjectType()) { + + case NPC: + npc = (NPC) target; + contract = npc.getContract(); + break; + case Mob: + mob = (Mob) target; + contract = mob.getContract(); + break; + default: + throwbackInfo(pc, "NpcSlot: target must be an NPC"); + return; + } + + // User requests list of current groups. + + if (args[0].equalsIgnoreCase("LIST")) { + + outString = "Current: " + contract.getAllowedBuildings(); + + throwbackInfo(pc, outString); + return; + } + + if (validateUserInput(args) == false) { + this.sendUsage(pc); + return; + } + + // Extract the building group flag from user input + + buildingGroup = BuildingGroup.valueOf(args[0].toUpperCase()); + + switch (args[1].toUpperCase()) { + + case "ON": + contract.getAllowedBuildings().add(buildingGroup); + + if (!DbManager.ContractQueries.updateAllowedBuildings(contract, contract.getAllowedBuildings().toLong())) { + Logger.error("Failed to update Database for Contract Allowed buildings"); + ChatManager.chatSystemError(pc, "Failed to update Database for Contract Allowed buildings. " + + "Contact A CCR, oh wait, you are a CCR. You're Fubared."); + return; + } + + throwbackInfo(pc, "SlotNpc " + buildingGroup.name() + " added to npc"); + break; + case "OFF": + contract.getAllowedBuildings().remove(buildingGroup); + if (!DbManager.ContractQueries.updateAllowedBuildings(contract, contract.getAllowedBuildings().toLong())) { + Logger.error("Failed to update Database for Contract Allowed buildings"); + ChatManager.chatSystemError(pc, "Failed to update Database for Contract Allowed buildings. " + + "Contact A CCR, oh wait, you are a CCR. You're Fubared."); + return; + } + + throwbackInfo(pc, "SlotNpc " + buildingGroup.name() + " removed from npc"); + break; + } + + } + + @Override + protected String _getHelpString() { + return "Sets a building slot on a targeted npc"; + } + + // Class methods + + @Override + protected String _getUsageString() { + String usage = "/npcslot [BuildingType] on-off \n"; + + for (BuildingGroup group : BuildingGroup.values()) { + usage += group.name() + ' '; + } + + usage = StringUtils.wordWrap(usage, 30); + + return usage; + } + +} diff --git a/src/engine/devcmd/cmds/SlotTestCmd.java b/src/engine/devcmd/cmds/SlotTestCmd.java new file mode 100644 index 00000000..bc31e7c2 --- /dev/null +++ b/src/engine/devcmd/cmds/SlotTestCmd.java @@ -0,0 +1,93 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; + +import java.util.ArrayList; + +public class SlotTestCmd extends AbstractDevCmd { + + public SlotTestCmd() { + super("slottest"); + } + + @Override + protected void _doCmd(PlayerCharacter playerCharacter, String[] args, + AbstractGameObject target) { + + ArrayList buildingLocations; + String outString = "Available Slots\r\n"; + + if (target == null) + return; + + if (target.getObjectType() != GameObjectType.Building) + return; + + Building building = (Building) target; + + buildingLocations = BuildingManager._slotLocations.get(building.meshUUID); + + if (buildingLocations == null) { + outString = "No slot information for mesh: " + building.meshUUID; + ChatManager.chatSystemInfo(playerCharacter, outString); + return; + } + + // Goto slot location + + if (args[0].isEmpty() == false) { + + int slot = Integer.parseInt(args[0]); + Vector3fImmutable slotLocation; + BuildingLocation buildingLocation = BuildingManager._slotLocations.get(building.meshUUID).get(slot - 1); + slotLocation = building.getLoc().add(buildingLocation.getLocation()); + slotLocation = Vector3fImmutable.rotateAroundPoint(building.getLoc(), slotLocation, building.getBounds().getQuaternion().angleY); + playerCharacter.teleport(slotLocation); + return; + } + + for (BuildingLocation buildingLocation : BuildingManager._slotLocations.get(building.meshUUID)) + outString += buildingLocation.getSlot() + buildingLocation.getLocation().toString() + "\r\n"; + + outString += "\r\nNext Available Slot: " + BuildingManager.getAvailableSlot(building); + + if (building.getHirelings().isEmpty() == false) { + + outString += "\r\n\r\n"; + outString += "Hirelings List:"; + + for (AbstractCharacter hireling : building.getHirelings().keySet()) + outString += "\r\n" + hireling.getName() + " slot : " + building.getHirelings().get(hireling); + + } + + ChatManager.chatSystemInfo(playerCharacter, outString); + + } + + @Override + protected String _getHelpString() { + return "Displays slot information for building"; + } + + @Override + protected String _getUsageString() { + return "./slottest n"; + + } + +} diff --git a/src/engine/devcmd/cmds/SplatMobCmd.java b/src/engine/devcmd/cmds/SplatMobCmd.java new file mode 100644 index 00000000..cb429b99 --- /dev/null +++ b/src/engine/devcmd/cmds/SplatMobCmd.java @@ -0,0 +1,165 @@ +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * @author Summary: Game designer utility command to create multiple + * mobiles of a given UUID within a supplied range + */ + +public class SplatMobCmd extends AbstractDevCmd { + + // Instance variables + + private int _mobileUUID; + private int _mobileCount; + private float _targetRange; + private Vector3fImmutable _currentLocation; + + // Concurrency support + + private ReadWriteLock lock = new ReentrantReadWriteLock(); + + // Constructor + + public SplatMobCmd() { + super("splatmob"); + } + + + // AbstractDevCmd Overridden methods + + private static boolean validateUserInput(String[] userInput) { + + // incorrect number of arguments test + + if (userInput.length != 3) + return false; + + // Test for UUID conversion to int + + try { + Integer.parseInt(userInput[0]); + } catch (NumberFormatException | NullPointerException e) { + return false; + } + + + // Test for Number of Mobs conversion to int + + try { + Integer.parseInt(userInput[1]); + } catch (NumberFormatException | NullPointerException e) { + return false; + } + + // Test if range argument can convert to a float + + try { + Float.parseFloat(userInput[2]); + } catch (NumberFormatException | NullPointerException e) { + return false; + } + + return true; + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + + // Member variables + + Vector3fImmutable mobileLocation; + Mob mobile; + Zone serverZone; + + // Concurrency write lock due to instance variable usage + + lock.writeLock().lock(); + + try { + + // Validate user input + + if (validateUserInput(args) == false) { + this.sendUsage(pc); + return; + } + + // Parse user input + + parseUserInput(args); + + // Arguments have been validated and parsed at this point + // Begin creating mobiles + + _currentLocation = pc.getLoc(); + serverZone = ZoneManager.findSmallestZone(_currentLocation); + + for (int i = 0; i < _mobileCount; i++) { + + mobile = Mob.createMob(_mobileUUID, + Vector3fImmutable.getRandomPointInCircle(_currentLocation, _targetRange), + null, true, serverZone, null, 0, "", 1); + + if (mobile != null) { + mobile.updateDatabase(); + } + } + + } // End Try Block + + // Release Reentrant lock + + finally { + lock.writeLock().unlock(); + } + } + + @Override + protected String _getHelpString() { + return "Spawns multiple mobiles with a given range"; + } + + // Class methods + + @Override + protected String _getUsageString() { + return "/splatmob UUID [Count <= 100] [range <= 1200]"; + } + + private void parseUserInput(String[] userInput) { + + // Clear previous values + + _mobileUUID = 0; + _mobileCount = 0; + _targetRange = 0f; + + // Parse first argument into mobile UID. + + _mobileUUID = Integer.parseInt(userInput[0]); + + // Parse second argument into mobile count. Cap at 100 mobs. + + _mobileCount = Integer.parseInt(userInput[1]); + _mobileCount = Math.min(_mobileCount, 100); + + // Parse third argument into range. Cap at 200 units. + + _targetRange = Float.parseFloat(userInput[2]); + _targetRange = Math.min(_targetRange, 1200f); + + } + +} \ No newline at end of file diff --git a/src/engine/devcmd/cmds/SqlDebugCmd.java b/src/engine/devcmd/cmds/SqlDebugCmd.java new file mode 100644 index 00000000..5e9b9a28 --- /dev/null +++ b/src/engine/devcmd/cmds/SqlDebugCmd.java @@ -0,0 +1,84 @@ +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + +/** + * @author Summary: Devcmd to toggle logging of mysql statements + */ + +public class SqlDebugCmd extends AbstractDevCmd { + + // Instance variables + + + public SqlDebugCmd() { + super("sqldebug"); + } + + + // AbstractDevCmd Overridden methods + + private static boolean validateUserInput(String[] userInput) { + + int stringIndex; + String commandSet = "onoff"; + + // incorrect number of arguments test + + if (userInput.length != 1) + return false; + + // Validate arguments + + stringIndex = commandSet.indexOf(userInput[0].toLowerCase()); + + return stringIndex != -1; + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + + Boolean debugState = false; + + if (validateUserInput(args) == false) { + this.sendUsage(pc); + return; + } + + // Arguments have been validated use argument to set debug state + + switch (args[0]) { + case "on": + debugState = true; + break; + case "off": + debugState = false; + break; + default: + break; + } + + MBServerStatics.DB_ENABLE_QUERY_OUTPUT = debugState; + + // Send results to user + throwbackInfo(pc, "SQL debug state: " + debugState.toString()); + } + + @Override + protected String _getHelpString() { + return "Toggles sending SQL statements to log"; + } + + // Class methods + + @Override + protected String _getUsageString() { + return "/sqldebug on|off"; + } + + +} diff --git a/src/engine/devcmd/cmds/SummonCmd.java b/src/engine/devcmd/cmds/SummonCmd.java new file mode 100644 index 00000000..184903ca --- /dev/null +++ b/src/engine/devcmd/cmds/SummonCmd.java @@ -0,0 +1,113 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.SessionManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.net.client.msg.RecvSummonsRequestMsg; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; + +public class SummonCmd extends AbstractDevCmd { + + public SummonCmd() { + super("summon"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] args, + AbstractGameObject target) { + // Arg Count Check + if (args.length != 1) { + this.sendUsage(pc); + return; + } + PlayerCharacter pcToSummon = null; + + + if (args[0].equalsIgnoreCase("all")) { + for (PlayerCharacter toSummon : SessionManager.getAllActivePlayerCharacters()) { + Zone zone = ZoneManager.findSmallestZone(pc.getLoc()); + String location = "Somewhere"; + if (zone != null) + location = zone.getName(); + RecvSummonsRequestMsg rsrm = new RecvSummonsRequestMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), pc.getFirstName(), + location, false); + toSummon.getClientConnection().sendMsg(rsrm); + + } + return; + } + // 1-9 numeric digits, must be playerID + if (args[0].matches("\\d{1,9}?")) { + try { + int playerID = Integer.parseInt(args[0]); + pcToSummon = SessionManager + .getPlayerCharacterByID(playerID); + + if (pcToSummon == null) { + this.throwbackError(pc, "Character not found by ID: " + + playerID); + return; + } + } catch (NumberFormatException e) { + this.throwbackError(pc, "Supplied ID: '" + args[0] + + "' failed to parse to an INT"); + return; + + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to summon '" + + args[0] + "'by ID"); + return; + } + + } else { // player name + try { + pcToSummon = SessionManager + .getPlayerCharacterByLowerCaseName(args[0]); + if (pcToSummon == null) { + this.throwbackError(pc, "Character not found by name: " + + args[0]); + return; + } + } catch (Exception e) { + this.throwbackError(pc, + "An unknown exception occurred while attempting to summon '" + + args[0] + "'by name"); + return; + } + } + this.setTarget(pcToSummon); //for logging + + Vector3fImmutable loc = pc.getLoc(); + pcToSummon.teleport(loc); + + this.throwbackInfo(pc, "Player " + pcToSummon.getCombinedName() + + " has been summoned to your location."); + this.throwbackInfo(pcToSummon, + "You have been transported to another location."); + } + + @Override + protected String _getHelpString() { + return "Summons 'character' TO your current position. Can summon by character's first name or by the character's characterID."; + + } + + @Override + protected String _getUsageString() { + return "' /summon characterName' || ' /summon characterID'"; + } + +} diff --git a/src/engine/devcmd/cmds/SysMsgCmd.java b/src/engine/devcmd/cmds/SysMsgCmd.java new file mode 100644 index 00000000..3b619b86 --- /dev/null +++ b/src/engine/devcmd/cmds/SysMsgCmd.java @@ -0,0 +1,67 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +/** + * + */ + +public class SysMsgCmd extends AbstractDevCmd { + + public SysMsgCmd() { + super("sysmsg"); + } + + @Override + protected void _doCmd(PlayerCharacter pcSender, String[] args, + AbstractGameObject target) { + if (args.length != 1 || args[0].isEmpty()) { + this.sendUsage(pcSender); + return; + } + String text = "[System Admin Message]: " + args[0]; + ChatManager.chatSystemChannel(text); + } + + /** + * This function is called by the DevCmdManager. Override to avoid splitting + * argString into String array, since sysmsg displays full String as + * message, then calls the subclass specific _doCmd method. + */ + + @Override + public void doCmd(PlayerCharacter pcSender, String argString, + AbstractGameObject target) { + String[] args = new String[1]; + args[0] = argString; + + if (pcSender == null) { + return; + } + + this._doCmd(pcSender, args, target); + } + + @Override + protected String _getUsageString() { + return "' /sysmsg message'"; + } + + @Override + protected String _getHelpString() { + return "Send system message in chat window to all players"; + } + +} diff --git a/src/engine/devcmd/cmds/TeleportModeCmd.java b/src/engine/devcmd/cmds/TeleportModeCmd.java new file mode 100644 index 00000000..89bac199 --- /dev/null +++ b/src/engine/devcmd/cmds/TeleportModeCmd.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.PlayerCharacter; + +public class TeleportModeCmd extends AbstractDevCmd { + + public TeleportModeCmd() { + super("teleportMode"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + boolean newTeleportMode = false; + if (words.length == 0) { // toggle + newTeleportMode = !pc.isTeleportMode(); + + } else if (words[0].equalsIgnoreCase("on")) { + newTeleportMode = true; + + } else if (words[0].equalsIgnoreCase("off")) { + newTeleportMode = false; + + } else { + this.sendUsage(pc); + return; + } + pc.setTeleportMode(newTeleportMode); + this.setTarget(pc); //for logging + String output = (newTeleportMode ? "on" : "off"); + + throwbackInfo(pc, "Teleport mode is now '" + output + "'."); + } + + @Override + protected String _getHelpString() { + return "'on' enables teleport mode, 'off' disables. No arguments to the /teleportMode command will toggle the setting."; + } + + @Override + protected String _getUsageString() { + return "' /teleportMode [on | off]'"; + } + +} diff --git a/src/engine/devcmd/cmds/UnloadFurnitureCmd.java b/src/engine/devcmd/cmds/UnloadFurnitureCmd.java new file mode 100644 index 00000000..e926ccbc --- /dev/null +++ b/src/engine/devcmd/cmds/UnloadFurnitureCmd.java @@ -0,0 +1,101 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.net.client.msg.LoadStructureMsg; +import engine.net.client.msg.MoveToPointMsg; +import engine.net.client.msg.UnloadObjectsMsg; +import engine.objects.AbstractGameObject; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +/** + * @author + */ +public class UnloadFurnitureCmd extends AbstractDevCmd { + + public UnloadFurnitureCmd() { + super("furniture"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + if (target.getObjectType() != GameObjectType.Building) { + this.throwbackError(pc, "Must be targeting a building to load/unload furniture."); + return; + } + if (words[0].equalsIgnoreCase("unload")) { + + UnloadObjectsMsg uom = new UnloadObjectsMsg(); + for (AbstractWorldObject awo : pc.getLoadedStaticObjects()) { + if (awo.getObjectType() != GameObjectType.Building) + continue; + + Building awoBuilding = (Building) awo; + + if (!awoBuilding.isFurniture) + continue; + + if (awoBuilding.parentBuildingID != target.getObjectUUID()) + continue; + + MoveToPointMsg msg = new MoveToPointMsg(awoBuilding); + pc.getClientConnection().sendMsg(msg); + + uom.addObject(awoBuilding); + + + } + + pc.getClientConnection().sendMsg(uom); + + } else if (words[0].equalsIgnoreCase("load")) { + LoadStructureMsg lsm = new LoadStructureMsg(); + + for (AbstractWorldObject awo : pc.getLoadedStaticObjects()) { + if (awo.getObjectType() != GameObjectType.Building) + continue; + + Building awoBuilding = (Building) awo; + + if (!awoBuilding.isFurniture) + continue; + + if (awoBuilding.parentBuildingID != target.getObjectUUID()) + continue; + + lsm.addObject(awoBuilding); + + + } + + pc.getClientConnection().sendMsg(lsm); + + } + } + + @Override + protected String _getHelpString() { + String help = "Enchants an item with a prefix and suffix"; + return help; + } + + @Override + protected String _getUsageString() { + String usage = "' /enchant clear/Enchant1 Enchant2 Enchant3 ...'"; + return usage; + } + +} diff --git a/src/engine/devcmd/cmds/ZoneInfoCmd.java b/src/engine/devcmd/cmds/ZoneInfoCmd.java new file mode 100644 index 00000000..09f03b83 --- /dev/null +++ b/src/engine/devcmd/cmds/ZoneInfoCmd.java @@ -0,0 +1,149 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.AbstractGameObject; +import engine.objects.City; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; +import engine.util.StringUtils; + +import java.util.ArrayList; + + +/** + * @author + */ +public class ZoneInfoCmd extends AbstractDevCmd { + + public ZoneInfoCmd() { + super("zoneinfo"); + } + + @Override + protected void _doCmd(PlayerCharacter player, String[] words, + AbstractGameObject target) { + // Arg Count Check + Zone zone = null; + + if (player == null) { + throwbackError(player, "Unable to find the pc making the request."); + return; + } + + try { + int targetID = Integer.parseInt(words[0]); + + //try get zone by objectUUID + zone = ZoneManager.getZoneByUUID(targetID); + + //that failed, try get by zoneID + if (zone == null) + zone = ZoneManager.getZoneByZoneID(targetID); + + //no zone found, so fail + if (zone == null) { + throwbackError(player, "Zone with ID " + targetID + "not found"); + return; + } + } catch (Exception e) { + zone = ZoneManager.findSmallestZone(player.getLoc()); + } + + if (zone == null) { + throwbackError(player, "Zone not found"); + return; + } + + String newline = "\r\n "; + + + int objectUUID = zone.getObjectUUID(); + String output; + + output = "Target Information:" + newline; + output += StringUtils.addWS("UUID: " + objectUUID, 20); + output += newline; + output += "name: " + zone.getName(); + output += newline; + output += "loadNum: " + zone.getLoadNum(); + if (zone.getParent() != null) { + output += StringUtils.addWS(", parent: " + zone.getParent().getObjectUUID(), 30); + output += "Parentabs: x: " + zone.getParent().getAbsX() + ", y: " + zone.getParent().getAbsY() + ", z: " + zone.getParent().getAbsZ(); + + } else + output += StringUtils.addWS(", parent: none", 30); + output += newline; + output += "absLoc: x: " + zone.getAbsX() + ", y: " + zone.getAbsY() + ", z: " + zone.getAbsZ(); + output += newline; + output += "offset: x: " + zone.getXCoord() + ", y: " + zone.getYCoord() + ", z: " + zone.getZCoord(); + output += newline; + output += "radius: x: " + zone.getBounds().getHalfExtents().x + ", z: " + zone.getBounds().getHalfExtents().y; + output += newline; + + if (zone.getHeightMap() != null) { + output += "HeightMap ID: " + zone.getHeightMap().getHeightMapID(); + output += newline; + output += "Bucket Width X : " + zone.getHeightMap().getBucketWidthX(); + output += newline; + output += "Bucket Width Y : " + zone.getHeightMap().getBucketWidthY(); + + } + output += "radius: x: " + zone.getBounds().getHalfExtents().x + ", z: " + zone.getBounds().getHalfExtents().y; + output += newline; + // output += "minLvl = " + zone.getMinLvl() + " | maxLvl = " + zone.getMaxLvl(); + output += newline; + output += "Sea Level = " + zone.getSeaLevel(); + output += newline; + output += "World Altitude = " + zone.getWorldAltitude(); + throwbackInfo(player, output); + + City city = ZoneManager.getCityAtLocation(player.getLoc()); + + output += newline; + output += (city == null) ? "None" : city.getParent().getName(); + + if (city != null) { + + if (city.isLocationOnCityGrid(player.getLoc())) + output += " (Grid)"; + else if (city.isLocationOnCityZone(player.getLoc())) + output += " (Zone)"; + else if (city.isLocationWithinSiegeBounds(player.getLoc())) + output += " (Siege)"; + } else { + output = "children:"; + + ArrayList nodes = zone.getNodes(); + + if (nodes.isEmpty()) + output += " none"; + + for (Zone child : nodes) { + output += newline; + output += child.getName() + " (" + child.getLoadNum() + ')'; + } + } + throwbackInfo(player, output); + } + + @Override + protected String _getHelpString() { + return "Gets information on an Object."; + } + + @Override + protected String _getUsageString() { + return "' /info targetID'"; + } + +} diff --git a/src/engine/devcmd/cmds/ZoneSetCmd.java b/src/engine/devcmd/cmds/ZoneSetCmd.java new file mode 100644 index 00000000..2d6b3a56 --- /dev/null +++ b/src/engine/devcmd/cmds/ZoneSetCmd.java @@ -0,0 +1,83 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ZoneManager; +import engine.objects.*; + +public class ZoneSetCmd extends AbstractDevCmd { + + public ZoneSetCmd() { + super("zoneset"); + } + + @Override + protected void _doCmd(PlayerCharacter playerCharacter, String[] words, + AbstractGameObject target) { + Zone zone; + + if (playerCharacter == null) + return; + + if (words.length == 0) { + throwbackError(playerCharacter, "Usage: zoneset npc/mob"); + return; + } + + if (!words[0].equalsIgnoreCase("npc") && + !words[0].equalsIgnoreCase("mob")) { + throwbackError(playerCharacter, "Usage: zoneset npc/mob"); + return; + } + + zone = ZoneManager.findSmallestZone(playerCharacter.getLoc()); + + throwbackInfo(playerCharacter, zone.getName() + " (" + zone.getLoadNum() + ") " + zone.getObjectUUID()); + + // NPC + + if (words[0].equalsIgnoreCase("mob")) { + + for (NPC npc : zone.zoneNPCSet) { + String out = npc.getName() + '(' + npc.getDBID() + "): "; + throwbackInfo(playerCharacter, out); + } + return; + } + + // Mob + + for (Mob mob : zone.zoneMobSet) { + + String out = mob.getName() + '(' + mob.getDBID() + "): "; + + if (mob.isAlive()) + out += mob.getLoc().x + "x" + mob.getLoc().z + "; isAlive: " + mob.isAlive(); + else + out += " isAlive: " + mob.isAlive(); + + throwbackInfo(playerCharacter, out); + return; + } + + } + + @Override + protected String _getUsageString() { + return "' /zoneset npc|mob'"; + } + + @Override + protected String _getHelpString() { + return "lists entries in zone npc set"; + } + +} diff --git a/src/engine/devcmd/cmds/aiInfoCmd.java b/src/engine/devcmd/cmds/aiInfoCmd.java new file mode 100644 index 00000000..e31060bb --- /dev/null +++ b/src/engine/devcmd/cmds/aiInfoCmd.java @@ -0,0 +1,103 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.objects.AbstractGameObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; + +import java.util.Map; + + +/** + * @author + */ +public class aiInfoCmd extends AbstractDevCmd { + + public aiInfoCmd() { + super("aiinfo"); + } + + @Override + protected void _doCmd(PlayerCharacter playerCharacter, String[] words, + AbstractGameObject target) { + + // Arg Count Check + + if (words.length != 1) { + this.sendUsage(playerCharacter); + return; + } + + if (playerCharacter == null) + return; + + String newline = "\r\n "; + + + GameObjectType objType = target.getObjectType(); + String output; + + if (objType != GameObjectType.Mob) { + output = "Please Select A Mob For AI Info" + newline; + throwbackInfo(playerCharacter, output); + return; + } + + Mob mob = (Mob) target; + output = "Mob AI Information:" + newline; + output += mob.getName() + newline; + if (mob.BehaviourType != null) { + output += "BehaviourType: " + mob.BehaviourType.toString() + newline; + if (mob.BehaviourType.BehaviourHelperType != null) { + output += "Behaviour Helper Type: " + mob.BehaviourType.BehaviourHelperType.toString() + newline; + } else { + output += "Behaviour Helper Type: NULL" + newline; + } + output += "Wimpy: " + mob.BehaviourType.isWimpy + newline; + output += "Agressive: " + mob.BehaviourType.isAgressive + newline; + output += "Can Roam: " + mob.BehaviourType.canRoam + newline; + output += "Calls For Help: " + mob.BehaviourType.callsForHelp + newline; + output += "Responds To Call For Help: " + mob.BehaviourType.respondsToCallForHelp + newline; + } else { + output += "BehaviourType: NULL" + newline; + } + output += "Aggro Range: " + mob.getAggroRange() + newline; + output += "Player Aggro Map Size: " + mob.playerAgroMap.size() + newline; + if (mob.playerAgroMap.size() > 0) { + output += "Players Loaded:" + newline; + } + for (Map.Entry entry : mob.playerAgroMap.entrySet()) { + output += "Player ID: " + entry.getKey() + " Hate Value: " + (PlayerCharacter.getPlayerCharacter(entry.getKey())).getHateValue() + newline; + } + if (mob.getCombatTarget() != null) + output += "Current Target: " + mob.getCombatTarget().getName() + newline; + else + output += "Current Target: NULL" + newline; + + for (int token : mob.mobPowers.keySet()) + output += token + newline; + + throwbackInfo(playerCharacter, output); + } + + @Override + protected String _getHelpString() { + return "Gets AI information on a Mob."; + } + + @Override + protected String _getUsageString() { + return "' /aiinfo targetID'"; + } + +} \ No newline at end of file diff --git a/src/engine/devcmd/cmds/convertLoc.java b/src/engine/devcmd/cmds/convertLoc.java new file mode 100644 index 00000000..e794e3fd --- /dev/null +++ b/src/engine/devcmd/cmds/convertLoc.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.devcmd.cmds; + +import engine.Enum.GameObjectType; +import engine.devcmd.AbstractDevCmd; +import engine.gameManager.ChatManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +public class convertLoc extends AbstractDevCmd { + + public convertLoc() { + super("convertLoc"); + } + + @Override + protected void _doCmd(PlayerCharacter pc, String[] words, + AbstractGameObject target) { + + + if (target == null) { + Vector3fImmutable convertLoc = ZoneManager.findSmallestZone(pc.getLoc()).getLoc().subtract(pc.getLoc()); + ChatManager.chatSystemInfo(pc, Vector3fImmutable.toString(convertLoc)); + return; + } + + + if (target.getObjectType() != GameObjectType.Building) + return; + Building toConvert = (Building) target; + Vector3fImmutable convertedLoc = ZoneManager.convertWorldToLocal(toConvert, pc.getLoc()); + ChatManager.chatSystemInfo(pc, Vector3fImmutable.toString(convertedLoc)); + + + } + + @Override + protected String _getHelpString() { + return "Temporarily Changes SubRace"; + } + + @Override + protected String _getUsageString() { + return "' /setBuildingCollidables add/remove 'add creates a collision line.' needs 4 integers. startX, endX, startY, endY"; + + } + +} diff --git a/src/engine/exception/FactoryBuildException.java b/src/engine/exception/FactoryBuildException.java new file mode 100644 index 00000000..319bf2ba --- /dev/null +++ b/src/engine/exception/FactoryBuildException.java @@ -0,0 +1,32 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.exception; + +public class FactoryBuildException extends MBServerException { + + private static final long serialVersionUID = 2331961867931593523L; + + public FactoryBuildException() { + super(); + } + + public FactoryBuildException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public FactoryBuildException(String arg0) { + super(arg0); + } + + public FactoryBuildException(Throwable arg0) { + super(arg0); + } + +} diff --git a/src/engine/exception/MBServerException.java b/src/engine/exception/MBServerException.java new file mode 100644 index 00000000..3bd77543 --- /dev/null +++ b/src/engine/exception/MBServerException.java @@ -0,0 +1,38 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.exception; + +import org.pmw.tinylog.Logger; + + +public abstract class MBServerException extends Exception { + private static final long serialVersionUID = 3878845236025977250L; + + public MBServerException() { + super(); + } + + public MBServerException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public MBServerException(String arg0) { + super(arg0); + } + + public MBServerException(Throwable arg0) { + super(arg0); + } + + public void logException(String origin) { + Logger.error(origin, this.getClass().getSimpleName() + "(1): " + this.getMessage()); + + } +} diff --git a/src/engine/exception/MsgSendException.java b/src/engine/exception/MsgSendException.java new file mode 100644 index 00000000..f789402f --- /dev/null +++ b/src/engine/exception/MsgSendException.java @@ -0,0 +1,31 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.exception; + +public class MsgSendException extends MBServerException { + private static final long serialVersionUID = 6927044139998382254L; + + public MsgSendException() { + super(); + } + + public MsgSendException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public MsgSendException(String arg0) { + super(arg0); + } + + public MsgSendException(Throwable arg0) { + super(arg0); + } + +} diff --git a/src/engine/exception/SerializationException.java b/src/engine/exception/SerializationException.java new file mode 100644 index 00000000..2381e619 --- /dev/null +++ b/src/engine/exception/SerializationException.java @@ -0,0 +1,31 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.exception; + +public class SerializationException extends MBServerException { + private static final long serialVersionUID = 6927044139998382254L; + + public SerializationException() { + super(); + } + + public SerializationException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public SerializationException(String arg0) { + super(arg0); + } + + public SerializationException(Throwable arg0) { + super(arg0); + } + +} diff --git a/src/engine/gameManager/BuildingManager.java b/src/engine/gameManager/BuildingManager.java new file mode 100644 index 00000000..5633df05 --- /dev/null +++ b/src/engine/gameManager/BuildingManager.java @@ -0,0 +1,790 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.gameManager; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.GameObjectType; +import engine.InterestManagement.InterestManager; +import engine.InterestManagement.WorldGrid; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.UpgradeBuildingJob; +import engine.math.Bounds; +import engine.math.Vector3fImmutable; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.ManageCityAssetsMsg; +import engine.net.client.msg.PlaceAssetMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +public enum BuildingManager { + + BUILDINGMANAGER; + + public static HashMap> _stuckLocations = new HashMap<>(); + public static HashMap> _slotLocations = new HashMap<>(); + + public static int getAvailableSlot(Building building) { + + ArrayList slotLocations = _slotLocations.get(building.meshUUID); + + // Some meshes might not have slot locations assigned. + + if (slotLocations == null || + slotLocations.isEmpty()) + return -1; + + int numOfSlots = _slotLocations.get(building.meshUUID).size(); + + for (int i = 1; i <= numOfSlots; i++) { + + if (!building.getHirelings().containsValue(i)) + return i; + } + return -1; + } + + public static int getLastAvailableSlot(Building building) { + + ArrayList slotLocations = _slotLocations.get(building.meshUUID); + + // Some meshes might not have slot locations assigned. + + if (slotLocations == null || + slotLocations.isEmpty()) + return -1; + + int numOfSlots = _slotLocations.get(building.meshUUID).size(); + + for (int i = numOfSlots; i > 0; i--) { + + if (!building.getHirelings().containsValue(i)) + return i; + } + return -1; + } + + public static BuildingLocation getSlotLocation(Building building, int slot) { + + BuildingLocation buildingLocation = new BuildingLocation(); + + if (slot == -1) + return buildingLocation; + + buildingLocation = _slotLocations.get(building.meshUUID).get(slot - 1); // array index + + if (buildingLocation == null) { + Logger.error("Invalid slot for building: " + building.getObjectUUID()); + } + + return buildingLocation; + } + + public static boolean playerCanManage(PlayerCharacter player, Building building) { + + if (player == null) + return false; + + if (building == null) + return false; + + + if (building.getRank() == -1) + return false; + + if (IsOwner(building, player)) + return true; + + //individual friend. + if (building.getFriends().get(player.getObjectUUID()) != null) + return true; + + //Admin's can access stuff + + if (player.isCSR()) + return true; + + //Guild stuff + + + if (building.getGuild() != null && building.getGuild().isGuildLeader(player.getObjectUUID())) + return true; + + if (building.getFriends().get(player.getGuild().getObjectUUID()) != null + && building.getFriends().get(player.getGuild().getObjectUUID()).getFriendType() == 8) + return true; + + if (building.getFriends().get(player.getGuild().getObjectUUID()) != null + && building.getFriends().get(player.getGuild().getObjectUUID()).getFriendType() == 9 + && GuildStatusController.isInnerCouncil(player.getGuildStatus())) + return true; + + if (Guild.sameGuild(building.getGuild(), player.getGuild()) && GuildStatusController.isInnerCouncil(player.getGuildStatus())) + return true; + + if (Guild.sameGuild(building.getGuild(), player.getGuild()) && GuildStatusController.isGuildLeader(player.getGuildStatus())) + return true; + + return false; + + //TODO test friends list once added + //does not meet above criteria. Cannot access. + } + + public static boolean playerCanManageNotFriends(PlayerCharacter player, Building building) { + + //Player Can only Control Building if player is in Same Guild as Building and is higher rank than IC. + + if (player == null) + return false; + + if (building == null) + return false; + + + if (building.getRank() == -1) + return false; + + if (IsOwner(building, player)) + return true; + + //Somehow guild leader check fails? lets check if Player is true Guild GL. + if (building.getGuild() != null && building.getGuild().isGuildLeader(player.getObjectUUID())) + return true; + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) + return false; + + + return false; + + } + + public static synchronized boolean lootBuilding(PlayerCharacter player, Building building) { + + if (building == null) + return false; + + if (player == null) + return false; + + if (building.getRank() != -1) + return false; + + if (building.getBlueprintUUID() == 0) + return false; + + switch (building.getBlueprint().getBuildingGroup()) { + case SHRINE: + Shrine shrine = Shrine.shrinesByBuildingUUID.get(building.getObjectUUID()); + if (shrine == null) + return false; + + int amount = shrine.getFavors(); + //no more favors too loot! + if (amount == 0) { + + try { + ErrorPopupMsg.sendErrorPopup(player, 166); + } catch (Exception e) { + + } + return false; + } + + ItemBase elanIB = ItemBase.getItemBase(1705032); + + if (elanIB == null) + return false; + + if (!player.getCharItemManager().hasRoomInventory(elanIB.getWeight())) + return false; + + if (!Item.MakeItemForPlayer(elanIB, player, amount)) + return false; + + shrine.setFavors(0); + break; + case WAREHOUSE: + + Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(building.getObjectUUID()); + + if (warehouse == null) + return false; + + for (ItemBase resourceBase : ItemBase.getResourceList()) { + if (!player.getCharItemManager().hasRoomInventory(resourceBase.getWeight())) { + ChatManager.chatSystemInfo(player, "You can not carry any more of that item."); + return false; + } + if (warehouse.getResources().get(resourceBase) == null) + continue; + + int resourceAmount = warehouse.getResources().get(resourceBase); + + if (resourceAmount <= 0) + continue; + + if (warehouse.loot(player, resourceBase, resourceAmount, true)) + ChatManager.chatInfoInfo(player, "You have looted " + resourceAmount + ' ' + resourceBase.getName()); + } + break; + + } + //Everything was looted, Maybe we should + return true; + } + + //This method restarts an upgrade timer when a building is loaded from the database. + // Submit upgrade job for this building based upon it's current upgradeDateTime + + public static void submitUpgradeJob(Building building) { + + if (building == null) + return; + + + if (building.getUpgradeDateTime() == null) { + Logger.error("Attempt to submit upgrade job for non-ranking building"); + return; + } + + // Submit upgrade job for future date or current instant + + if (building.getUpgradeDateTime().isAfter(LocalDateTime.now())) { + JobContainer jc = JobScheduler.getInstance().scheduleJob(new UpgradeBuildingJob(building), + building.getUpgradeDateTime().atZone(ZoneId.systemDefault()) + .toInstant().toEpochMilli()); + } else + JobScheduler.getInstance().scheduleJob(new UpgradeBuildingJob(building), 0); + } + + public static void setUpgradeDateTime(Building building, LocalDateTime upgradeDateTime, int rankCost) { + + if (building == null) + return; + if (!DbManager.BuildingQueries.updateBuildingUpgradeTime(upgradeDateTime, building, rankCost)) { + Logger.error("Failed to set upgradeTime for building " + building.getObjectUUID()); + return; + } + + building.upgradeDateTime = upgradeDateTime; + + } + + // Method transfers ownership of all hirelings in a building + + public static void refreshHirelings(Building building) { + + if (building == null) + return; + + Guild newGuild; + + if (building.getOwner() == null) + newGuild = Guild.getErrantGuild(); + else + newGuild = building.getOwner().getGuild(); + + for (AbstractCharacter hireling : building.getHirelings().keySet()) { + hireling.setGuild(newGuild); + WorldGrid.updateObject(hireling); + } + + } + + public static void cleanupHirelings(Building building) { + + // Early exit: Cannot have hirelings in a building + // without a blueprint. + + if (building.getBlueprintUUID() == 0) + return; + + // Remove all hirelings for destroyed buildings + + if (building.getRank() < 1) { + + for (AbstractCharacter slottedNPC : building.getHirelings().keySet()) { + + if (slottedNPC.getObjectType() == Enum.GameObjectType.NPC) + ((NPC) slottedNPC).remove(); + else if (slottedNPC.getObjectType() == Enum.GameObjectType.Mob) + NPCManager.removeMobileFromBuilding(((Mob) slottedNPC), building); + } + return; + } + + // Delete hireling if building has deranked. + for (AbstractCharacter hireling : building.getHirelings().keySet()) { + + NPC npc = null; + Mob mob = null; + + if (hireling.getObjectType() == Enum.GameObjectType.NPC) + npc = (NPC) hireling; + else if (hireling.getObjectType() == Enum.GameObjectType.Mob) + mob = (Mob) hireling; + + if (building.getHirelings().get(hireling) > building.getBlueprint().getSlotsForRank(building.getRank())) + + if (npc != null) { + if (!npc.remove()) + Logger.error("Failed to remove npc " + npc.getObjectUUID() + + "from Building " + building.getObjectUUID()); + else + building.getHirelings().remove(npc); + } else if (mob != null) { + if (!NPCManager.removeMobileFromBuilding(mob, building)) + Logger.error("Failed to remove npc " + npc.getObjectUUID() + + "from Building " + building.getObjectUUID()); + else + building.getHirelings().remove(npc); + } + + } + + refreshHirelings(building); + } + + public static Building getBuilding(int id) { + + if (id == 0) + return null; + + Building building; + + building = (Building) DbManager.getFromCache(Enum.GameObjectType.Building, id); + + if (building != null) + return building; + + return DbManager.BuildingQueries.GET_BUILDINGBYUUID(id); + + } + + public static boolean PlayerCanControlNotOwner(Building building, PlayerCharacter player) { + + if (player == null) + return false; + + if (building == null) + return false; + + if (building.getOwner() == null) + return false; + + //lets pass true if player is owner anyway. + if (building.getOwner().equals(player)) + return true; + + if (player.getGuild().isEmptyGuild()) + return false; + + if (building.getGuild().isGuildLeader(player.getObjectUUID())) + return true; + + if (!Guild.sameGuild(building.getGuild(), player.getGuild())) + return false; + + return GuildStatusController.isGuildLeader(player.getGuildStatus()) != false || GuildStatusController.isInnerCouncil(player.getGuildStatus()) != false; + } + + //This is mainly used for Rolling and gold sharing between building and warehouse. + + public static int GetWithdrawAmountForRolling(Building building, int cost) { + + //all funds are available to roll. + + if (cost <= GetAvailableGold(building)) + return cost; + + // cost is more than available gold, return available gold + + return GetAvailableGold(building); + + } + + public static int GetAvailableGold(Building building) { + + if (building.getStrongboxValue() == 0) + return 0; + + if (building.getStrongboxValue() < building.reserve) + return 0; + + return building.getStrongboxValue() - building.reserve; + } + + public static int GetOverdraft(Building building, int cost) { + int availableGold = GetWithdrawAmountForRolling(building, cost); + return cost - availableGold; + } + + public static boolean IsPlayerHostile(Building building, PlayerCharacter player) { + + //Nation Members and Guild members are not hostile. + // if (building.getGuild() != null){ + // if (pc.getGuild() != null) + // if (building.getGuild().getObjectUUID() == pc.getGuildUUID() + // || pc.getGuild().getNation().getObjectUUID() == building.getGuild().getNation().getObjectUUID()) + // return false; + // } + if (Guild.sameNationExcludeErrant(building.getGuild(), player.getGuild())) + return false; + + if (!building.reverseKOS) { + + Condemned condemn = building.getCondemned().get(player.getObjectUUID()); + + if (condemn != null && condemn.isActive()) + return true; + + if (player.getGuild() != null) { + + Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID()); + + if (guildCondemn != null && guildCondemn.isActive()) + return true; + + Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID()); + return nationCondemn != null && nationCondemn.isActive() && nationCondemn.getFriendType() == Condemned.NATION; + } else { + //TODO ADD ERRANT KOS CHECK + } + } else { + + Condemned condemn = building.getCondemned().get(player.getObjectUUID()); + + if (condemn != null && condemn.isActive()) + return false; + + if (player.getGuild() != null) { + + Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID()); + + if (guildCondemn != null && guildCondemn.isActive()) + return false; + + Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID()); + return nationCondemn == null || !nationCondemn.isActive() || nationCondemn.getFriendType() != Condemned.NATION; + } else { + //TODO ADD ERRANT KOS CHECK + } + return true; + } + + //When we get to here, This means The building was not reverse KOS + //and passed the hostile test. + + return false; + } + + public static final synchronized boolean addHirelingForWorld(Building building, PlayerCharacter contractOwner, Vector3fImmutable NpcLoc, Zone zone, Contract NpcID, int rank) { + + String pirateName = NPCManager.getPirateName(NpcID.getMobbaseID()); + + NPC npc = null; + + npc = NPC.createNPC(pirateName, NpcID.getObjectUUID(), NpcLoc, null, zone, (short) rank, building); + + if (npc == null) + return false; + + npc.setObjectTypeMask(MBServerStatics.MASK_NPC); + npc.setLoc(npc.bindLoc); + InterestManager.setObjectDirty(npc); + return true; + + } + + public static synchronized boolean addHireling(Building building, PlayerCharacter contractOwner, Zone zone, Contract contract, Item item) { + + int rank = 1; + + if (building.getBlueprintUUID() == 0) + return false; + + if (building.getBlueprint().getMaxSlots() == building.getHirelings().size()) + return false; + + String pirateName = NPCManager.getPirateName(contract.getMobbaseID()); + + if (item.getChargesRemaining() > 0) + rank = item.getChargesRemaining() * 10; + else + rank = 10; + + Mob mob; + NPC npc; + + if (NPC.ISWallArcher(contract)) { + + mob = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), true, zone, building, contract.getContractID(), pirateName, rank); + + if (mob == null) + return false; + + mob.setLoc(mob.getLoc()); + + return true; + } + + if (NPC.ISGuardCaptain(contract.getContractID())) { + + mob = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), true, zone, building, contract.getContractID(), pirateName, rank); + + if (mob == null) + return false; + + mob.setLoc(mob.getLoc()); + + return true; + } + if (contract.getContractID() == 910) { + + //guard dog + mob = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), true, zone, building, contract.getContractID(), pirateName, rank); + + if (mob == null) + return false; + + mob.setLoc(mob.getLoc()); + + return true; + } + + npc = NPC.createNPC(pirateName, contract.getObjectUUID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, (short) rank, building); + + if (npc == null) + return false; + + npc.setObjectTypeMask(MBServerStatics.MASK_NPC); + npc.setLoc(npc.bindLoc); + InterestManager.setObjectDirty(npc); + return true; + } + + public static boolean IsWallPiece(Building building) { + + if (building.getBlueprint() == null) + return false; + + BuildingGroup buildingGroup = building.getBlueprint().getBuildingGroup(); + + switch (buildingGroup) { + case WALLSTRAIGHT: + case WALLCORNER: + case SMALLGATE: + case ARTYTOWER: + case WALLSTRAIGHTTOWER: + case WALLSTAIRS: + return true; + default: + return false; + } + } + + public static Building getBuildingFromCache(int id) { + return (Building) DbManager.getFromCache(GameObjectType.Building, id); + } + + public static boolean IsOwner(Building building, PlayerCharacter player) { + if (building == null || player == null) + return false; + + if (building.getOwner() == null) + return false; + + + return building.getOwner().getObjectUUID() == player.getObjectUUID(); + + } + + public static float GetMissingHealth(Building building) { + return building.healthMax - building.getCurrentHitpoints(); + } + + public static int GetRepairCost(Building building) { + return (int) (GetMissingHealth(building) * .10f); + } + + public static Regions GetRegion(Building building, float x, float y, float z) { + if (building.getBounds() == null) + return null; + + if (building.getBounds().getRegions() == null) + return null; + + Regions currentRegion = null; + for (Regions region : building.getBounds().getRegions()) { + + if (region.isPointInPolygon(new Vector3fImmutable(x, y, z))) { + if (y > (region.highLerp.y - 5)) + currentRegion = region; + } + } + return currentRegion; + } + + public static Regions GetRegion(Building building, int room, int level, float x, float z) { + if (building.getBounds() == null) + return null; + + if (building.getBounds().getRegions() == null) + return null; + + for (Regions region : building.getBounds().getRegions()) { + + if (region.getLevel() != level) + continue; + if (region.getRoom() != room) + continue; + + if (region.isPointInPolygon(new Vector3fImmutable(x, 0, z))) { + return region; + } + } + return null; + } + + public static Vector3fImmutable GetBindLocationForBuilding(Building building) { + + Vector3fImmutable bindLoc = null; + + if (building == null) + return Enum.Ruins.getRandomRuin().getLocation(); + + + bindLoc = building.getLoc(); + + + float radius = Bounds.meshBoundsCache.get(building.getMeshUUID()).radius; + if (building.getRank() == 8) { + bindLoc = building.getStuckLocation(); + if (bindLoc != null) + return bindLoc; + } + + float x = bindLoc.getX(); + float z = bindLoc.getZ(); + float offset = ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * radius; + int direction = ThreadLocalRandom.current().nextInt(4); + + switch (direction) { + case 0: + x += radius; + z += offset; + break; + case 1: + x += offset; + z -= radius; + break; + case 2: + x -= radius; + z += offset; + break; + case 3: + x += offset; + z += radius; + break; + } + bindLoc = new Vector3fImmutable(x, bindLoc.getY(), z); + + return bindLoc; + + + } + + public static void processRedeedNPC(NPC npc, Building building, ClientConnection origin) { + + // Member variable declaration + PlayerCharacter player; + Contract contract; + CharacterItemManager itemMan; + ItemBase itemBase; + Item item; + + npc.lock.writeLock().lock(); + + try { + + + if (building == null) + return; + player = SessionManager.getPlayerCharacter(origin); + itemMan = player.getCharItemManager(); + + contract = npc.getContract(); + + if (!player.getCharItemManager().hasRoomInventory((short) 1)) { + ErrorPopupMsg.sendErrorPopup(player, 21); + return; + } + + + if (!building.getHirelings().containsKey(npc)) + return; + + if (!npc.remove()) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return; + } + + building.getHirelings().remove(npc); + + itemBase = ItemBase.getItemBase(contract.getContractID()); + + if (itemBase == null) { + Logger.error("Could not find Contract for npc: " + npc.getObjectUUID()); + return; + } + + boolean itemWorked = false; + + item = new Item(itemBase, player.getObjectUUID(), Enum.OwnerType.PlayerCharacter, (byte) ((byte) npc.getRank() - 1), (byte) ((byte) npc.getRank() - 1), + (short) 1, (short) 1, true, false, Enum.ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + item.setNumOfItems(1); + item.containerType = Enum.ItemContainerType.INVENTORY; + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + itemWorked = true; + } catch (Exception e) { + Logger.error(e); + } + if (itemWorked) { + itemMan.addItemToInventory(item); + itemMan.updateInventory(); + } + + ManageCityAssetsMsg mca = new ManageCityAssetsMsg(); + mca.actionType = NPC.SVR_CLOSE_WINDOW; + mca.setTargetType(building.getObjectType().ordinal()); + mca.setTargetID(building.getObjectUUID()); + origin.sendMsg(mca); + + } catch (Exception e) { + Logger.error(e); + } finally { + npc.lock.writeLock().unlock(); + } + + } +} diff --git a/src/engine/gameManager/ChatManager.java b/src/engine/gameManager/ChatManager.java new file mode 100644 index 00000000..496cfb6c --- /dev/null +++ b/src/engine/gameManager/ChatManager.java @@ -0,0 +1,1232 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.gameManager; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.BaneRecord; +import engine.db.archive.PvpRecord; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.MessageDispatcher; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.net.client.msg.chat.*; +import engine.net.client.msg.commands.ClientAdminCommandMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import engine.server.world.WorldServer; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public enum ChatManager { + + CHATMANAGER; + + // Sending a quantity of (FLOOD_QTY_THRESHOLD) messages within + // (FLOOD_TIME_THRESHOLD) ms will flag as a flood message + // Example, set to 3 & 2000 to flag the 3rd message within 2000 ms. + + private static final int FLOOD_QTY_THRESHOLD = 3; + private static final int FLOOD_TIME_THRESHOLD = 2000; + private static final String FLOOD_USER_ERROR = "You talk too much!"; + private static final String SILENCED = "You find yourself mute!"; + private static final String UNKNOWN_COMMAND = "No such command."; + + /** + * This method used when handling a ChatMsg received from the network. + */ + public static void handleChatMsg(Session sender, AbstractChatMsg msg) { + + if (msg == null) { + Logger.warn( + "null message: "); + return; + } + + if (sender == null) { + Logger.warn( + "null sender: "); + return; + } + + PlayerCharacter pc = sender.getPlayerCharacter(); + + if (pc == null) { + Logger.warn( + "invalid sender: "); + return; + } + + Protocol protocolMsg = msg.getProtocolMsg(); + + // Flood control, implemented per channel + + boolean isFlood = false; + long curMsgTime = System.currentTimeMillis(); + long checkTime = pc.chatFloodTime(protocolMsg.opcode, curMsgTime, FLOOD_QTY_THRESHOLD - 1); + + if ((checkTime > 0L) && (curMsgTime - checkTime < FLOOD_TIME_THRESHOLD)) + isFlood = true; + + switch (protocolMsg) { + case CHATSAY: + ChatManager.chatSay(pc, msg.getMessage(), isFlood); + return; + case CHATCSR: + ChatManager.chatCSR(msg); + return; + case CHATTELL: + ChatTellMsg ctm = (ChatTellMsg) msg; + ChatManager.chatTell(pc, ctm.getTargetName(), ctm.getMessage(), isFlood); + return; + case CHATSHOUT: + ChatManager.chatShout(pc, msg.getMessage(), isFlood); + return; + case CHATGUILD: + ChatManager.chatGuild(pc, (ChatGuildMsg) msg); + return; + case CHATGROUP: + ChatManager.chatGroup(pc, (ChatGroupMsg) msg); + return; + case CHATIC: + ChatManager.chatIC(pc, (ChatICMsg) msg); + return; + case LEADERCHANNELMESSAGE: + ChatManager.chatGlobal(pc, msg.getMessage(), isFlood); + return; + case GLOBALCHANNELMESSAGE: + case CHATPVP: + case CHATCITY: + case CHATINFO: + case SYSTEMBROADCASTCHANNEL: + default: + } + + } + + /* + * Channels + */ + /* + * CSR + */ + public static void chatCSR(AbstractChatMsg msg) { + PlayerCharacter sender = (PlayerCharacter) msg.getSource(); + if (sender == null) + return; + // if (promotionClass == null) + // return false; + // int promotionClassID = promotionClass.getUUID(); + // switch (promotionClassID) { + // case 2901: + // case 2902: + // case 2903: + // case 2904: + // case 2910: + // return true; + // } + // return false; + if (!sender.isCSR) { + ChatManager.chatSystemError(sender, UNKNOWN_COMMAND); + return; + } + ArrayList distroList = new ArrayList<>(); + for (PlayerCharacter pc : SessionManager + .getAllActivePlayerCharacters()) { + if (pc.getAccount().status.equals(Enum.AccountStatus.BANNED) == false) + distroList.add(pc); + } + // Send dispatch to each player + + for (AbstractWorldObject abstractWorldObject : distroList) { + PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject; + Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + } + + public static boolean testSilenced(PlayerCharacter pc) { + + PlayerBonuses bonus = pc.getBonuses(); + + if (bonus != null && bonus.getBool(ModType.Silenced, SourceType.None)) { + ChatManager.chatSayError(pc, SILENCED); + return true; + } + return false; + } + + /* + * Say + */ + public static void chatSay(AbstractWorldObject player, String text, boolean isFlood) { + + PlayerCharacter pcSender = null; + + if (player.getObjectType() == GameObjectType.PlayerCharacter) + pcSender = (PlayerCharacter) player; + + // Parser eats all dev commands + + if (isFlood) { + ChatManager.chatSayError(pcSender, FLOOD_USER_ERROR); + return; + } + + if (ChatManager.isDevCommand(text) == true) { + ChatManager.processDevCommand(player, text); + return; + } + + if (ChatManager.isUpTimeRequest(text) == true) { + sendSystemMessage(pcSender, WorldServer.getUptimeString()); + return; + } + + if (ChatManager.isVersionRequest(text) == true) { + sendSystemMessage(pcSender, ConfigManager.MB_WORLD_GREETING.getValue()); + return; + } + + if (ChatManager.isNetStatRequest(text) == true) { + sendSystemMessage(pcSender, MessageDispatcher.getNetstatString()); + return; + } + + // Needs to be refactored + + if (ChatManager.isPopulationRequest(text) == true) { + sendSystemMessage(pcSender, SimulationManager.getPopulationString()); + return; + } + + if (ChatManager.isPvpRequest(text) == true) { + sendSystemMessage(pcSender, PvpRecord.getPvpHistoryString(pcSender.getObjectUUID())); + return; + } + + if (ChatManager.isBaneRequest(text) == true) { + sendSystemMessage(pcSender, BaneRecord.getBaneHistoryString()); + return; + } + + if (pcSender != null && testSilenced(pcSender)) + return; + + // Make the Message + ChatSayMsg chatSayMsg = new ChatSayMsg(player, text); + DispatchMessage.dispatchMsgToInterestArea(pcSender, chatSayMsg, Enum.DispatchChannel.SECONDARY, MBServerStatics.SAY_RANGE, true, true); + + } + + public static void sendSystemMessage(PlayerCharacter targetPlayer, String messageString) { + + ChatSystemMsg msg = new ChatSystemMsg(null, messageString); + msg.setMessageType(4); + msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID()); + Dispatch dispatch = Dispatch.borrow(targetPlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + /* + * Shout + */ + public static void chatShout(AbstractWorldObject sender, String text, + boolean isFlood) { + + PlayerCharacter pcSender = null; + + if (sender.getObjectType().equals(GameObjectType.PlayerCharacter)) + pcSender = (PlayerCharacter) sender; + + if (isFlood) { + ChatManager.chatSayError(pcSender, FLOOD_USER_ERROR); + return; + } + + if (pcSender != null && testSilenced(pcSender)) + return; + + // Make the Message + ChatShoutMsg msg = new ChatShoutMsg(sender, text); + DispatchMessage.dispatchMsgToInterestArea(pcSender, msg, engine.Enum.DispatchChannel.SECONDARY, MBServerStatics.SHOUT_RANGE, true, true); + + } + + public static void chatGlobal(PlayerCharacter sender, String text, + boolean isFlood) { + + PlayerCharacter pcSender = null; + + if (sender.getObjectType().equals(GameObjectType.PlayerCharacter)) + pcSender = (PlayerCharacter) sender; + + if (isFlood) { + ChatManager.chatSayError(pcSender, FLOOD_USER_ERROR); + return; + } + + if (pcSender != null && testSilenced(pcSender)) + return; + +// Make the Message + ChatGlobalMsg msg = new ChatGlobalMsg(sender, text); + DispatchMessage.dispatchMsgToAll(sender, msg, true); + + } + + /* + * Tell + */ + public static void chatTell(AbstractWorldObject sender, String recipient, + String text, boolean isFlood) { + if (text.isEmpty()) + return; + + PlayerCharacter pcSender = null; + + if (sender.getObjectType().equals(GameObjectType.PlayerCharacter)) + pcSender = (PlayerCharacter) sender; + + if (pcSender != null && testSilenced(pcSender)) + return; + + PlayerCharacter pcRecip = SessionManager + .getPlayerCharacterByLowerCaseName(recipient); + + if (pcRecip != null) { + if (isFlood) { + ChatManager.chatTellError(pcSender, FLOOD_USER_ERROR); + return; + } + + ChatManager.chatTell(sender, pcRecip, text); + } else + ChatManager.chatTellError((PlayerCharacter) sender, + "There is no such player."); + + } + + public static void chatTell(AbstractWorldObject sender, + AbstractWorldObject recipient, String text) { + + PlayerCharacter pcSender = null; + + if (sender.getObjectType().equals(GameObjectType.PlayerCharacter)) + pcSender = (PlayerCharacter) sender; + + if (pcSender != null && testSilenced(pcSender)) + return; + + // Verify we are sending to a PlayerCharacter + PlayerCharacter pcRecip; + if (recipient.getObjectType().equals(GameObjectType.PlayerCharacter)) + pcRecip = (PlayerCharacter) recipient; + else + return; + + ClientConnection cc = SessionManager.getClientConnection( + pcRecip); + + // make sure we have a good ClientConnection + if (cc != null) { + + ChatTellMsg chatTellMsg = new ChatTellMsg(sender, pcRecip, text); + + // Don't send to recipient if they're ignoring sender + if (!pcRecip.isIgnoringPlayer(pcSender)) { + // Send dispatch to each player + + Dispatch dispatch = Dispatch.borrow(pcRecip, chatTellMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + // Also send /tell to sender + if (pcSender != null) { + Dispatch dispatch = Dispatch.borrow(pcSender, chatTellMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + } else + ChatManager.chatTellError(pcSender, + "There is no such player."); + } + + public static void chatGuild(PlayerCharacter sender, ChatGuildMsg msg) { + + // Verify sender has PlayerCharacter + if (sender == null) + return; + + if (testSilenced(sender)) + return; + + // Verify player is in guild and get guild + Guild guild = sender.getGuild(); + if (guild == null || guild.getObjectUUID() == 0) + return; + + // Get Distro List for guild + ArrayList distroList = SessionManager.getActivePCsInGuildID(guild.getObjectUUID()); + if (msg.getUnknown02() == 5) { + + Guild nation = guild.getNation(); + if (nation == null) + return; + distroList = ChatManager.getNationListChat(nation, sender); + } + + // Check the DistroList size + if (distroList.size() < 1) { + ChatManager.chatGuildError(sender, + "You find yourself mute!"); + Logger.error("Guild Chat returned a list of Players <1 in length."); + return; + } + + // Make the Message + + // make the ed message + ChatGuildMsg chatGuildMsg = new ChatGuildMsg(msg); + + chatGuildMsg.setSourceType(sender.getObjectType().ordinal()); + chatGuildMsg.setSourceID(sender.getObjectUUID()); + chatGuildMsg.setSourceName(sender.getFirstName()); + chatGuildMsg.setUnknown03(WorldServer.worldMapID); // Server ID + chatGuildMsg.setUnknown04(sender.getGuild() != null ? sender.getGuild() + .getCharter() : 0); // Charter? + chatGuildMsg.setUnknown05(GuildStatusController.getTitle(sender.getGuildStatus())); // Title? + chatGuildMsg.setUnknown06(sender.getRace().getRaceType().getCharacterSex().equals(Enum.CharacterSex.MALE) ? 1 : 2); // isMale?, seen 1 and 2 + + // Send dispatch to each player + + for (AbstractWorldObject abstractWorldObject : distroList) { + PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject; + Dispatch dispatch = Dispatch.borrow(playerCharacter, chatGuildMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + } + + public static void chatIC(PlayerCharacter sender, ChatICMsg msg) { + + // Verify sender has PlayerCharacter + if (sender == null) + return; + + if (testSilenced(sender)) + return; + + // Verify player is in guild and get guild + Guild guild = sender.getGuild(); + if (guild == null || guild.getObjectUUID() == 0) + return; + + //Verify player is an IC + + if (GuildStatusController.isInnerCouncil(sender.getGuildStatus()) == false) + return; + + // Get Distro List for guild + HashSet distroList = ChatManager.getGuildICList(guild, sender); + + // Check the DistroList size + if (distroList.size() < 1) { + ChatManager.chatICError(sender, "You find yourself mute!"); + Logger.error("Guild Chat returned a list of Players <1 in length."); + return; + } + + // TODO Hrm, are we modifying the incoming message or making a response? + // Not anymore we aren't! + + // Create new outgoing message + + ChatICMsg chatICMsg = new ChatICMsg(msg); + + chatICMsg.setSourceType(sender.getObjectType().ordinal()); + chatICMsg.setSourceID(sender.getObjectUUID()); + chatICMsg.setSourceName(sender.getFirstName()); + chatICMsg.setUnknown02(WorldServer.worldMapID); // Server ID + chatICMsg.setUnknown03(GuildStatusController.getRank(sender.getGuildStatus())); // correct? + chatICMsg.setUnknown04(GuildStatusController.getTitle(sender.getGuildStatus())); // correct? + chatICMsg.setUnknown05(2); // unknown, seen 1 and 2 + + + // Send dispatch to each player + + for (AbstractWorldObject abstractWorldObject : distroList) { + PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject; + Dispatch dispatch = Dispatch.borrow(playerCharacter, chatICMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + } + + private static boolean isVersionRequest(String text) { + return text.equalsIgnoreCase("lua_version()"); + } + + private static boolean isPvpRequest(String text) { + return text.equalsIgnoreCase("lua_pvp()"); + } + + private static boolean isBaneRequest(String text) { + return text.equalsIgnoreCase("lua_banes()"); + } + + public static void chatGroup(PlayerCharacter sender, ChatGroupMsg msg) { + + // Verify sender has PlayerCharacter + if (sender == null) + return; + + if (testSilenced(sender)) + return; + + // Verify player is in guild and get guild + + Group group = GroupManager.getGroup(sender); + + if (group == null) + return; + + // Get Distro List for guild + HashSet distroList = ChatManager.getGroupList(group, sender); + + // Check the DistroList size + if (distroList.size() < 1) { + ChatManager.chatGroupError(sender, + "You find yourself mute!"); + Logger.error("Group Chat returned a list of Players <1 in length."); + return; + } + + // Make the Message + + ChatGroupMsg chatGroupMsg = new ChatGroupMsg(msg); + + chatGroupMsg.setSourceType(sender.getObjectType().ordinal()); + chatGroupMsg.setSourceID(sender.getObjectUUID()); + chatGroupMsg.setSourceName(sender.getFirstName()); + chatGroupMsg.setUnknown02(WorldServer.worldMapID); // Server ID + + + // Send dispatch to each player + + for (AbstractWorldObject abstractWorldObject : distroList) { + PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject; + Dispatch dispatch = Dispatch.borrow(playerCharacter, chatGroupMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + } + + public static void GuildEnterWorldMsg(PlayerCharacter sender, + ClientConnection origin) { + // Verify sender has PlayerCharacter + if (sender == null) + return; + // Verify player is in guild and get guild + Guild guild = sender.getGuild(); + if (guild == null || guild.getObjectUUID() == 0) + return; + // Get Distro List for guild + HashSet distroList = ChatManager.getGuildList(guild, null); + // Check the DistroList size + if (distroList.size() < 1) { + Logger.error("Guild EnterWorldMsg returned a list of Players <1 in length."); + return; + } + + // Make and send enter world message + GuildEnterWorldMsg msg = new GuildEnterWorldMsg(sender); + msg.setName(sender.getFirstName()); + msg.setGuildTitle(GuildStatusController.getTitle(sender.getGuildStatus())); + msg.setGuildUUID(guild.getObjectUUID()); + msg.setCharter(guild.getCharter()); + + // Send dispatch to each player + + for (AbstractWorldObject abstractWorldObject : distroList) { + PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject; + Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + } + + public static void chatPeekSteal(PlayerCharacter sender, AbstractCharacter tar, Item item, boolean success, boolean detect, int amount) { + if (sender == null || tar == null) + return; + + PlayerCharacter target = null; + + if (tar.getObjectType().equals(GameObjectType.PlayerCharacter)) + target = (PlayerCharacter) tar; + + // Get Distro List + HashSet distroList = WorldGrid + .getObjectsInRangePartial(sender.getLoc(), + MBServerStatics.SAY_RANGE, MBServerStatics.MASK_PLAYER); + + // Check the DistroList size + if (distroList.size() < 1) + return; + + //remove Thief and Victim from other's list + int size = distroList.size(); + for (int i = size - 1; i > -1; i--) { + AbstractWorldObject awo = (AbstractWorldObject) distroList.toArray()[i]; + if (awo.getObjectUUID() == sender.getObjectUUID()) + distroList.remove(awo); + else if (awo.getObjectUUID() == tar.getObjectUUID()) + distroList.remove(awo); + + } + + String textToThief = ""; + String textToVictim = ""; + String textToOthers = ""; + + if (item != null) //Steal + if (success) { + String name = ""; + if (item.getItemBase() != null) + if (item.getItemBase().getUUID() == 7) + name = amount + " gold "; + else { + String vowels = "aeiou"; + String iName = item.getItemBase().getName(); + if (iName.length() > 0) + if (vowels.indexOf(iName.substring(0, 1).toLowerCase()) >= 0) + name = "an " + iName + ' '; + else + name = "a " + iName + ' '; + } + textToThief = "You have stolen " + name + "from " + tar.getFirstName() + '!'; + textToVictim = sender.getFirstName() + "is caught with thier hands in your pocket!"; + //textToOthers = sender.getFirstName() + " steals " + name + "from " + target.getFirstName() + "."; + } else + textToThief = "Your attempt at stealing failed..."; //textToVictim = sender.getFirstName() + " fails to steal from you."; + //textToOthers = sender.getFirstName() + " fails to steal from " + target.getFirstName() + "."; + else //Peek + if (success) { + if (detect) { + textToThief = tar.getFirstName() + " catches you peeking through their belongings!"; + textToVictim = "You catch " + sender.getFirstName() + " peeking through your belongings!"; + } + } else { + textToThief = "Your attempt at peeking failed..."; + textToVictim = sender.getFirstName() + " is seen eyeing up your backpack..."; + textToOthers = sender.getFirstName() + " is seen eyeing up the backpack of " + tar.getFirstName() + "..."; + } + + //Send msg to thief + HashSet senderList = new HashSet<>(); + senderList.add(sender); + if (!textToThief.isEmpty()) + ChatManager.chatSystemSend(senderList, textToThief, 1, 2); + + if (target != null && !textToVictim.isEmpty()) { + HashSet victimList = new HashSet<>(); + victimList.add(target); + ChatManager.chatSystemSend(victimList, textToVictim, 1, 2); + } + + //Send msg to Others in range\ + if (!textToOthers.isEmpty()) + ChatManager.chatSystemSend(distroList, textToOthers, 1, 2); + } + + // Send System Announcement as a flash at top of screen + public static void chatSystemFlash(String text) { + + chatSystem(null, text, 2, 1); + } + + public static void chatSystemChannel(String text) { + chatSystem(null, text, 1, 4); // Type 4 simply displays text as is + } + + // Send Error Message to player + public static void chatSystemError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 1); + } + + public static void chatSystemInfo(PlayerCharacter pc, String text) { + sendInfoMsgToPlayer(pc, text, 1); + } + + public static void chatCommanderError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 3); + } + + public static void chatNationError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 5); + } + + public static void chatLeaderError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 6); + } + + public static void chatShoutError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 7); + } + + public static void chatInfoError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 10); + } + + public static void chatGuildError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 12); + } + + public static void chatICError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 13); + } + + public static void chatGroupError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 14); + } + + public static void chatCityError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 15); + } + + public static void chatSayError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 16); + } + + public static void chatEmoteError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 17); + } + + public static void chatTellError(PlayerCharacter pc, String text) { + sendErrorMsgToPlayer(pc, text, 19); + } + + // Send Info Message to channels + public static void chatCommanderInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 3, 2); + } + + public static void chatNationInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 5, 2); + } + + public static void chatNationInfo(Guild nation, String text) { + chatSystemGuild(nation, text, 5, 2); + } + + public static void chatLeaderInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 6, 2); + } + + public static void chatShoutInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 7, 2); + } + + public static void chatInfoInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 10, 2); + } + + public static void chatGuildInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 12, 2); + } + + public static void chatICInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 13, 2); + } + + public static void chatGroupInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 14, 2); + } + + public static void chatCityInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 15, 2); + } + + public static void chatSayInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 16, 2); + } + + public static void chatEmoteInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 17, 2); + } + + public static void chatTellInfo(PlayerCharacter pc, String text) { + chatSystem(pc, text, 19, 2); + } + + public static void chatGroupInfoCanSee(PlayerCharacter pc, String text) { + HashSet distroList = null; + + Group group = GroupManager.getGroup(pc); + if (group != null) { + distroList = ChatManager.getGroupList(group, pc); + if (distroList != null) { + Iterator it = distroList.iterator(); + while (it.hasNext()) { + AbstractWorldObject awo = it.next(); + if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) + it.remove(); + else { + PlayerCharacter pcc = (PlayerCharacter) awo; + if (pcc.getSeeInvis() < pc.getHidden()) + it.remove(); + } + } + } + } + ChatManager.chatSystemSend(distroList, text, 14, 2); + } + + // Send MOTD Message to channels + public static void chatNationMOTD(PlayerCharacter pc, String text) { + chatNationMOTD(pc, text, false); + } + + public static void chatNationMOTD(PlayerCharacter pc, String text, boolean all) { + if (all) // Send to all Nation + + chatSystem(pc, text, 5, 3); + else // Send to just pc + + chatSystemMOTD(pc, text, 5, 3); + } + + public static void chatGuildMOTD(PlayerCharacter pc, String text) { + chatGuildMOTD(pc, text, false); + } + + public static void chatGuildMOTD(PlayerCharacter pc, String text, boolean all) { + if (all) // Send to all Guild + + chatSystem(pc, text, 12, 3); + else // Send to just pc + + chatSystemMOTD(pc, text, 12, 3); + } + + public static void chatICMOTD(PlayerCharacter pc, String text) { + chatICMOTD(pc, text, false); + } + + public static void chatICMOTD(PlayerCharacter pc, String text, boolean all) { + if (all) // Send to all IC's + + chatSystem(pc, text, 13, 3); + else // Send to just pc + + chatSystemMOTD(pc, text, 13, 3); + } + + // Send Info Message to guild channel based on guild + public static void chatGuildInfo(Guild guild, String text) { + HashSet distroList = null; + if (guild != null) + distroList = ChatManager.getGuildList(guild, null); + ChatManager.chatSystemSend(distroList, text, 12, 2); + } + + public static void chatSystemMOTD(PlayerCharacter sender, String text, + int channel, int messageType) { + HashSet distroList = ChatManager.getOwnPlayer(sender); + ChatManager.chatSystemSend(distroList, text, channel, messageType); + } + + public static void chatPVP(String text) { + // Create message + ChatPvPMsg msg = new ChatPvPMsg(null, text); + DispatchMessage.dispatchMsgToAll(msg); + } + + public static void chatInfo(String text) { + HashSet distroList = ChatManager.getAllPlayers(null); + chatSystemSend(distroList, text, 1, 2); + } + + public static ChatSystemMsg CombatInfo(AbstractWorldObject source, AbstractWorldObject target) { + String text = "The " + target.getName() + " attacks " + source.getName(); + ChatSystemMsg msg = new ChatSystemMsg(null, text); + msg.setChannel(20); + msg.setMessageType(2); + return msg; + } + + public static void chatSystem(PlayerCharacter sender, String text, int channel, + int messageType) { + HashSet distroList = null; + if (channel == 1) // System Channel Message + + distroList = ChatManager.getAllPlayers(sender); + else if (channel == 2) // System Flash, send to everyone + + distroList = ChatManager.getAllPlayers(sender); + else if (channel == 3) { // Commander Channel + } else if (channel == 5) { // Nation Channel, get Nation list + Guild guild = sender.getGuild(); + if (guild != null) { + Guild nation = guild.getNation(); + if (nation != null) + if (nation.getObjectUUID() != 0) // Don't /nation to errant + // nation + + distroList = ChatManager.getNationList(nation, sender); + } + } else if (channel == 6) { // Leader Channel + } else if (channel == 7) // Shout Channel + distroList = ChatManager.getOwnPlayer(sender); + else if (channel == 10) // Info Channel + distroList = getOwnPlayer(sender); + else if (channel == 12) { // guild Channel, get Guild list + Guild guild = sender.getGuild(); + if (guild != null) + if (guild.getObjectUUID() != 0) // Don't /guild to errant guild + + distroList = ChatManager.getGuildList(guild, sender); + } else if (channel == 13) { // IC Channel, get Guild IC list + Guild guild = sender.getGuild(); + if (guild != null) + if (guild.getObjectUUID() != 0) // Don't /IC to errant guild + + distroList = ChatManager.getGuildICList(guild, sender); + } else if (channel == 14) { // Group Channel, get group list + Group group = GroupManager.getGroup(sender); + if (group != null) + distroList = ChatManager.getGroupList(group, sender); + } else if (channel == 15) { // City Channel, get people bound to city + // list + } else if (channel == 16) // Say Channel + distroList = ChatManager.getOwnPlayer(sender); + else if (channel == 17) { // Emote Channel, get say List + } else if (channel == 19) // Tell Channel + distroList = ChatManager.getOwnPlayer(sender); + else + return; + ChatManager.chatSystemSend(distroList, text, channel, messageType); + } + + public static void chatSystemGuild(Guild sender, String text, int channel, + int messageType) { + HashSet distroList = null; + + if (channel == 5) { // Nation Channel, get Nation list + if (sender != null) { + Guild nation = sender.getNation(); + if (nation != null) + if (nation.getObjectUUID() != 0) // Don't /nation to errant + // nation + + distroList = ChatManager.getNationList(nation, null); + } + } else if (channel == 12) { // guild Channel, get Guild list + if (sender != null) + if (sender.getObjectUUID() != 0) // Don't /guild to errant guild + + distroList = ChatManager.getGuildList(sender, null); + } else if (channel == 13) { // IC Channel, get Guild IC list + if (sender != null) + if (sender.getObjectUUID() != 0) // Don't /IC to errant guild + + distroList = ChatManager.getGuildICList(sender, null); + } else + return; + ChatManager.chatSystemSend(distroList, text, channel, messageType); + + } + + public static void chatSystemSend(HashSet distroList, + String text, int channel, int messageType) { + // verify someone in distroList to send message to + if (distroList == null) + return; + if (distroList.size() < 1) + return; + + // Create message + ChatSystemMsg chatSystemMsg = new ChatSystemMsg(null, text); + chatSystemMsg.setChannel(channel); + chatSystemMsg.setMessageType(messageType); + + // Send dispatch to each player + + for (AbstractWorldObject abstractWorldObject : distroList) { + PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject; + Dispatch dispatch = Dispatch.borrow(playerCharacter, chatSystemMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + } + + // Get distroList for guild + public static HashSet getGuildList(Guild guild, PlayerCharacter source) { + HashSet distroList = new HashSet<>(); + + for (PlayerCharacter playerCharacter : SessionManager.getAllActivePlayerCharacters()) { + + if (Guild.sameGuild(playerCharacter.getGuild(), guild)) { + if (source != null && playerCharacter.isIgnoringPlayer(source)) + continue; // dont add if recip has ignored source + distroList.add(playerCharacter); + } + } + return distroList; + } + + // Get distroList for guild IC's + public static HashSet getGuildICList(Guild guild, PlayerCharacter source) { + + HashSet distroList = new HashSet<>(); + + for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) { + + if (Guild.sameGuild(pc.getGuild(), guild)) + if (GuildStatusController.isInnerCouncil(pc.getGuildStatus())) { + if (source != null && pc.isIgnoringPlayer(source)) + continue; // dont add if recip has ignored source + distroList.add(pc); + } + } + return distroList; + } + + // Get distroList for group + public static HashSet getGroupList(Group group, PlayerCharacter source) { + HashSet distroList = new HashSet<>(); + Set players = group.getMembers(); + for (PlayerCharacter pc : players) { + if (source != null && pc.isIgnoringPlayer(source)) + continue; // dont add if recip has ignored source + distroList.add(pc); + } + return distroList; + } + + // Get distroList for nation + public static HashSet getNationList(Guild nation, PlayerCharacter source) { + HashSet distroList = new HashSet<>(); + + for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) { + + Guild guild = pc.getGuild(); + + if (guild != null) + if (guild.getNation().getObjectUUID() == nation.getObjectUUID()) { + if (source != null && pc.isIgnoringPlayer(source)) + continue; // dont add if recip has ignored source + distroList.add(pc); + } + } + return distroList; + } + + public static ArrayList getNationListChat(Guild nation, PlayerCharacter source) { + ArrayList distroList = new ArrayList<>(); + + for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) { + + Guild guild = pc.getGuild(); + + if (guild != null) + if (guild.getNation().getObjectUUID() == nation.getObjectUUID()) { + if (source != null && pc.isIgnoringPlayer(source)) + continue; // dont add if recip has ignored source + distroList.add(pc); + } + } + return distroList; + } + + // Get distroList for all players + public static HashSet getAllPlayers(PlayerCharacter source) { + + HashSet distroList = new HashSet<>(); + for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) { + if (source != null && pc.isIgnoringPlayer(source)) + continue; // dont add if recip has ignored source + distroList.add(pc); + } + return distroList; + } + + // Get just self for distrList + public static HashSet getOwnPlayer(PlayerCharacter pc) { + if (pc == null) + return null; + HashSet distroList = new HashSet<>(); + distroList.add(pc); + return distroList; + } + + /* + * Utils + */ + // Error Message for type channel + private static void sendErrorMsgToPlayer(AbstractCharacter player, String message, + int channel) { + if (player == null) + return; + ChatManager.sendSystemMsgToPlayer(player, message, channel, 1); + } + + // Info Message for type channel + private static void sendInfoMsgToPlayer(AbstractCharacter player, String message, + int channel) { + ChatManager.sendSystemMsgToPlayer(player, message, channel, 2); + } + + // Message of the Day Message for type channel + // private void sendMOTDMsgToPlayer(AbstractCharacter player, String message, int channel) { + // this.sendSystemMsgToPlayer(player, message, channel, 3); + // } + private static void sendSystemMsgToPlayer(AbstractCharacter player, + String message, int channel, int messageType) { + + PlayerCharacter playerCharacter; + + if (player == null) + return; + + if (player.getObjectType().equals(GameObjectType.PlayerCharacter) == false) { + Logger.error("Chat message sent to non player"); + return; + } + + // Wtf recasting this? If we're sending chat messages to players + // or mobiles, then something is really wrong. + + playerCharacter = (PlayerCharacter) player; + + ChatSystemMsg chatSystemMsg = new ChatSystemMsg(null, message); + chatSystemMsg.setMessageType(messageType); // Error message + chatSystemMsg.setChannel(channel); + + Dispatch dispatch = Dispatch.borrow(playerCharacter, chatSystemMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + private static boolean isDevCommand(String text) { + return text.startsWith(MBServerStatics.DEV_CMD_PREFIX); + } + + private static boolean isUpTimeRequest(String text) { + return text.equalsIgnoreCase("lua_uptime()"); + } + + private static boolean isNetStatRequest(String text) { + return text.equalsIgnoreCase("lua_netstat()"); + } + + private static boolean isPopulationRequest(String text) { + return text.equalsIgnoreCase("lua_population()"); + } + + private static boolean processDevCommand(AbstractWorldObject sender, String text) { + + if (sender.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + PlayerCharacter pcSender = (PlayerCharacter) sender; + + // first remove the DEV_CMD_PREFIX + String[] words = text.split(MBServerStatics.DEV_CMD_PREFIX, 2); + + if (words[1].length() == 0) + return false; + + // next get the command + String[] commands = words[1].split(" ", 2); + String cmd = commands[0].toLowerCase(); + String cmdArgument = ""; + + if (commands.length > 1) + cmdArgument = commands[1].trim(); + + AbstractGameObject target = pcSender.getLastTarget(); + // return DevCmd.processDevCommand(pcSender, cmd, cmdArgument, + // target); + return DevCmdManager.handleDevCmd(pcSender, cmd, + cmdArgument, target); + } + return false; + } + + /** + * Process an Admin Command, which is a preset command sent from the client + */ + public static void HandleClientAdminCmd(ClientAdminCommandMsg data, + ClientConnection cc) { + + PlayerCharacter pcSender = SessionManager.getPlayerCharacter(cc); + + if (pcSender == null) + return; + + Account acct = SessionManager.getAccount(pcSender); + + if (acct == null) + return; + + // require minimal access to continue + // specific accessLevel checks performed by the DevCmdManager + if (acct.status.equals(Enum.AccountStatus.ADMIN) == false) { + Logger.warn(pcSender.getFirstName() + " Attempted to use a client admin command"); + //wtf? ChatManager.chatSystemInfo(pcSender, "CHEATER!!!!!!!!!!!!!"); + return; + } + + // First remove the initial slash + String d = data.getMsgCommand(); + String[] words = d.split("/", 2); + + if (words[1].length() == 0) + return; + + // Next get the command + String[] commands = words[1].split(" ", 2); + String cmd = commands[0].toLowerCase(); + String cmdArgument = ""; + + if (commands.length > 1) + cmdArgument = commands[1].trim(); + + AbstractGameObject target = data.getTarget(); + + // Map to a DevCmd + String devCmd = ""; + + if (cmd.compareTo("goto") == 0) + devCmd = "goto"; + else if (cmd.compareTo("suspend") == 0) + devCmd = "suspend"; + else if (cmd.compareTo("getinfo") == 0) + devCmd = "info"; + else if (devCmd.isEmpty()) { + Logger.info("Unhandled admin command was used: /" + + cmd); + return; + } + + DevCmdManager.handleDevCmd(pcSender, devCmd, cmdArgument, + target); + } + + +} diff --git a/src/engine/gameManager/CombatManager.java b/src/engine/gameManager/CombatManager.java new file mode 100644 index 00000000..0587e384 --- /dev/null +++ b/src/engine/gameManager/CombatManager.java @@ -0,0 +1,1441 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum.*; +import engine.exception.MsgSendException; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.AttackJob; +import engine.jobs.DeferredPowerJob; +import engine.math.Vector3fImmutable; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import engine.powers.DamageShield; +import engine.powers.PowersBase; +import engine.powers.effectmodifiers.AbstractEffectModifier; +import engine.powers.effectmodifiers.WeaponProcEffectModifier; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; + +public enum CombatManager { + + COMBATMANAGER; + + public static int animation = 0; + + /** + * Message sent by player to attack something. + */ + public static void setAttackTarget(AttackCmdMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + int targetType; + AbstractWorldObject target; + + if (TargetedActionMsg.un2cnt == 60 || TargetedActionMsg.un2cnt == 70) + return; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return; + + //source must match player this account belongs to + + if (player.getObjectUUID() != msg.getSourceID() || player.getObjectType().ordinal() != msg.getSourceType()) { + Logger.error("Msg Source ID " + msg.getSourceID() + " Does not Match Player ID " + player.getObjectUUID()); + return; + } + + targetType = msg.getTargetType(); + + if (targetType == GameObjectType.PlayerCharacter.ordinal()) { + target = PlayerCharacter.getFromCache(msg.getTargetID()); + } else if (targetType == GameObjectType.Building.ordinal()) { + target = BuildingManager.getBuildingFromCache(msg.getTargetID()); + } else if (targetType == GameObjectType.Mob.ordinal()) { + target = Mob.getFromCache(msg.getTargetID()); + } else { + player.setCombatTarget(null); + return; //not valid type to attack + } + + // quit of the combat target is already the current combat target + // or there is no combat target + + if (target == null) + return; + + //set sources target + + player.setCombatTarget(target); + + //put in combat if not already + + if (!player.isCombat()) + toggleCombat(true, origin); + + //make character stand if sitting + + if (player.isSit()) + toggleSit(false, origin); + + AttackTarget(player, target); + + } + + public static void AttackTarget(PlayerCharacter playerCharacter, AbstractWorldObject target) { + + boolean swingOffhand = false; + + //check my weapon can I do an offhand attack + + Item weaponOff = playerCharacter.getCharItemManager().getEquipped().get(MBServerStatics.SLOT_OFFHAND); + Item weaponMain = playerCharacter.getCharItemManager().getEquipped().get(MBServerStatics.SLOT_MAINHAND); + + // if you carry something in the offhand thats a weapon you get to swing it + + if (weaponOff != null) + if (weaponOff.getItemBase().getType().equals(ItemType.WEAPON)) + swingOffhand = true; + + // if you carry nothing in either hand you get to swing your offhand + + if (weaponOff == null && weaponMain == null) + swingOffhand = true; + + + //we always swing our mainhand if we are not on timer + + JobContainer main = playerCharacter.getTimers().get("Attack" + MBServerStatics.SLOT_MAINHAND); + + // no timers on the mainhand, lets submit a job to swing + + if (main == null) + CombatManager.createTimer(playerCharacter, MBServerStatics.SLOT_MAINHAND, 1, true); // attack in 0.1 of a second + + /* + only swing offhand if we have a weapon in it or are unarmed in both hands + and no timers running + */ + + if (swingOffhand) { + + JobContainer off = playerCharacter.getTimers().get("Attack" + MBServerStatics.SLOT_OFFHAND); + + if (off == null) + CombatManager.createTimer(playerCharacter, MBServerStatics.SLOT_OFFHAND, 1, true); // attack in 0.1 of a second + } + + City playerCity = ZoneManager.getCityAtLocation(playerCharacter.getLoc()); + + if (playerCity != null && playerCity.getGuild().getNation().equals(playerCharacter.getGuild().getNation()) == false && playerCity.cityOutlaws.contains(playerCharacter.getObjectUUID()) == false) + playerCity.cityOutlaws.add(playerCharacter.getObjectUUID()); + } + + public static void setAttackTarget(PetAttackMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + Mob pet; + int targetType; + AbstractWorldObject target; + + if (TargetedActionMsg.un2cnt == 60 || TargetedActionMsg.un2cnt == 70) + return; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return; + + pet = player.getPet(); + + if (pet == null) + return; + + targetType = msg.getTargetType(); + + if (targetType == GameObjectType.PlayerCharacter.ordinal()) + target = PlayerCharacter.getFromCache(msg.getTargetID()); + else if (targetType == GameObjectType.Building.ordinal()) + target = BuildingManager.getBuildingFromCache(msg.getTargetID()); + else if (targetType == GameObjectType.Mob.ordinal()) + target = Mob.getFromCache(msg.getTargetID()); + else { + pet.setCombatTarget(null); + return; //not valid type to attack + } + + if (pet.equals(target)) + return; + + // quit of the combat target is already the current combat target + // or there is no combat target + + if (target == null || target == pet.getCombatTarget()) + return; + + //set sources target + + pet.setCombatTarget(target); + + //put in combat if not already + + if (!pet.isCombat()) + pet.setCombat(true); + + //make character stand if sitting + + if (pet.isSit()) + toggleSit(false, origin); + + } + + private static void removeAttackTimers(AbstractCharacter ac) { + + JobContainer main; + JobContainer off; + + if (ac == null) + return; + + main = ac.getTimers().get("Attack" + MBServerStatics.SLOT_MAINHAND); + off = ac.getTimers().get("Attack" + MBServerStatics.SLOT_OFFHAND); + + if (main != null) + JobScheduler.getInstance().cancelScheduledJob(main); + + ac.getTimers().remove("Attack" + MBServerStatics.SLOT_MAINHAND); + + if (off != null) + JobScheduler.getInstance().cancelScheduledJob(off); + + ac.getTimers().remove("Attack" + MBServerStatics.SLOT_OFFHAND); + + ac.setCombatTarget(null); + + } + + /** + * Begin Attacking + */ + public static void doCombat(AbstractCharacter ac, int slot) { + + int ret = 0; + + if (ac == null) + return; + + // Attempt to eat null targets until we can clean + // up this unholy mess and refactor it into a thread. + + ret = attemptCombat(ac, slot); + + //handle pets + if (ret < 2 && ac.getObjectType().equals(GameObjectType.Mob)) { + + Mob mob = (Mob) ac; + + if (mob.isPet()) + return; + } + + //ret values + //0: not valid attack, fail attack + //1: cannot attack, wrong hand + //2: valid attack + //3: cannot attack currently, continue checking + + if (ret == 0 || ret == 1) { + + //Could not attack, clear timer + + ConcurrentHashMap timers = ac.getTimers(); + + if (timers != null) + timers.remove("Attack" + slot); + + //clear combat target if not valid attack + + if (ret == 0) + ac.setCombatTarget(null); + + } else if (ret == 3) //Failed but continue checking. reset timer + createTimer(ac, slot, 5, false); + } + + /** + * Verify can attack target + */ + private static int attemptCombat(AbstractCharacter abstractCharacter, int slot) { + + if (abstractCharacter == null) + return 0; + + try { + + //Make sure player can attack + + PlayerBonuses bonus = abstractCharacter.getBonuses(); + + if (bonus != null && bonus.getBool(ModType.ImmuneToAttack, SourceType.None)) + return 0; + + AbstractWorldObject target = abstractCharacter.getCombatTarget(); + + if (target == null) + return 0; + + //target must be valid type + + if (AbstractWorldObject.IsAbstractCharacter(target)) { + + AbstractCharacter tar = (AbstractCharacter) target; + + //must be alive, attackable and in World + + if (!tar.isAlive()) + return 0; + else if (tar.isSafeMode()) + return 0; + else if (!tar.isActive()) + return 0; + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && abstractCharacter.getTimers().get("Attack" + slot) == null) + if (!((PlayerCharacter) abstractCharacter).canSee((PlayerCharacter) target)) + return 0; + + //must not be immune to all or immune to attack + + Resists res = tar.getResists(); + bonus = tar.getBonuses(); + + if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.ImmuneToAttack)) + if (res != null) + if (res.immuneToAll() || res.immuneToAttacks()) + return 0; + } else if (target.getObjectType().equals(GameObjectType.Building)) { + Building tar = (Building) target; + + // Cannot attack an invuln building + + if (tar.isVulnerable() == false) + return 0; + + } else + return 0; //only characters and buildings may be attacked + + //source must be in world and alive + + if (!abstractCharacter.isActive()) + return 0; + else if (!abstractCharacter.isAlive()) + return 0; + + //make sure source is in combat mode + + if (!abstractCharacter.isCombat()) + return 0; + + //See if either target is in safe zone + + if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectType().equals(GameObjectType.PlayerCharacter)) + if (((PlayerCharacter) abstractCharacter).inSafeZone() || ((PlayerCharacter) target).inSafeZone()) + return 0; + + if (!(slot == MBServerStatics.SLOT_MAINHAND || slot == MBServerStatics.SLOT_OFFHAND)) + return 0; + + if (abstractCharacter.getCharItemManager() == null) + return 0; + + //get equippment + + ConcurrentHashMap equipped = abstractCharacter.getCharItemManager().getEquipped(); + boolean hasNoWeapon = false; + + if (equipped == null) + return 0; + + //get Weapon + + boolean isWeapon = true; + Item weapon = equipped.get(slot); + ItemBase wb = null; + + if (weapon == null) + isWeapon = false; + else { + ItemBase ib = weapon.getItemBase(); + + if (ib == null || !ib.getType().equals(ItemType.WEAPON)) + isWeapon = false; + else + wb = ib; + } + + //no weapon, see if other hand has a weapon + + if (!isWeapon) + if (slot == MBServerStatics.SLOT_MAINHAND) { + + //make sure offhand has weapon, not shield + + Item weaponOff = equipped.get(MBServerStatics.SLOT_OFFHAND); + + if (weaponOff != null) { + ItemBase ib = weaponOff.getItemBase(); + + if (ib == null || !ib.getType().equals(ItemType.WEAPON)) + hasNoWeapon = true; + else + return 1; //no need to attack with this hand + + } else + hasNoWeapon = true; + + } else if (equipped.get(MBServerStatics.SLOT_MAINHAND) == null) + return 1; //no need to attack with this hand + + //Source can attack. + //NOTE Don't 'return;' beyond this point until timer created + + boolean attackFailure = (wb != null) && (wb.getRange() > 35f) && abstractCharacter.isMoving(); + + //Target can't attack on move with ranged weapons. + //if not enough stamina, then skip attack + + if (wb == null) { + if (abstractCharacter.getStamina() < 1) + attackFailure = true; + } else if (abstractCharacter.getStamina() < wb.getWeight()) + attackFailure = true; + + //see if attacker is stunned. If so, stop here + + bonus = abstractCharacter.getBonuses(); + + if (bonus != null && bonus.getBool(ModType.Stunned, SourceType.None)) + attackFailure = true; + + //Get Range of weapon + + float range; + + if (hasNoWeapon) + range = MBServerStatics.NO_WEAPON_RANGE; + else + range = getWeaponRange(wb, bonus); + + if (abstractCharacter.getObjectType() == GameObjectType.Mob) { + + Mob minion = (Mob) abstractCharacter; + + if (minion.isSiege()) + range = 300f; + } + + //Range check. + + if (NotInRange(abstractCharacter, target, range)) { + + //target is in stealth and can't be seen by source + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + if (!((PlayerCharacter) abstractCharacter).canSee((PlayerCharacter) target)) + return 0; + + attackFailure = true; + } + + //handle pet, skip timers (handled by AI) + + if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) { + + Mob mob = (Mob) abstractCharacter; + + if (mob.isPet()) { + attack(abstractCharacter, target, weapon, wb, slot == MBServerStatics.SLOT_MAINHAND); + return 2; + } + } + + //TODO Verify attacker has los (if not ranged weapon). + + if (!attackFailure) { + + if (hasNoWeapon || abstractCharacter.getObjectType().equals(GameObjectType.Mob)) + createTimer(abstractCharacter, slot, 20, true); //2 second for no weapon + else { + int wepSpeed = (int) (wb.getSpeed()); + + if (weapon != null && weapon.getBonusPercent(ModType.WeaponSpeed, SourceType.None) != 0f) //add weapon speed bonus + wepSpeed *= (1 + weapon.getBonus(ModType.WeaponSpeed, SourceType.None)); + + if (abstractCharacter.getBonuses() != null && abstractCharacter.getBonuses().getFloatPercentAll(ModType.AttackDelay, SourceType.None) != 0f) //add effects speed bonus + wepSpeed *= (1 + abstractCharacter.getBonuses().getFloatPercentAll(ModType.AttackDelay, SourceType.None)); + + if (wepSpeed < 10) + wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects. + + createTimer(abstractCharacter, slot, wepSpeed, true); + } + + attack(abstractCharacter, target, weapon, wb, slot == MBServerStatics.SLOT_MAINHAND); + } else + createTimer(abstractCharacter, slot, 5, false); // changed this to half a second to make combat attempts more aggressive than movement sync + + } catch (Exception e) { + return 0; + } + return 2; + } + + private static void createTimer(AbstractCharacter ac, int slot, int time, boolean success) { + + ConcurrentHashMap timers = ac.getTimers(); + + if (timers != null) { + AttackJob aj = new AttackJob(ac, slot, success); + JobContainer job; + job = JobScheduler.getInstance().scheduleJob(aj, (time * 100)); + timers.put("Attack" + slot, job); + } else { + Logger.error("Unable to find Timers for Character " + ac.getObjectUUID()); + } + } + + /** + * Attempt to attack target + */ + private static void attack(AbstractCharacter ac, AbstractWorldObject target, Item weapon, ItemBase wb, boolean mainHand) { + + float atr; + int minDamage, maxDamage; + int errorTrack = 0; + + try { + + if (ac == null) + return; + + if (target == null) + return; + + if (mainHand) { + atr = ac.getAtrHandOne(); + minDamage = ac.getMinDamageHandOne(); + maxDamage = ac.getMaxDamageHandOne(); + } else { + atr = ac.getAtrHandTwo(); + minDamage = ac.getMinDamageHandTwo(); + maxDamage = ac.getMaxDamageHandTwo(); + } + + boolean tarIsRat = false; + + if (target.getObjectTypeMask() == MBServerStatics.MASK_RAT) + tarIsRat = true; + else if (target.getObjectType() == GameObjectType.PlayerCharacter) { + + PlayerCharacter pTar = (PlayerCharacter) target; + + for (Effect eff : pTar.getEffects().values()) + if (eff.getPowerToken() == 429513599 || eff.getPowerToken() == 429415295) + tarIsRat = true; + } + + //Dont think we need to do this anymore. + + if (tarIsRat) + if (ac.getBonuses().getFloatPercentAll(ModType.Slay, SourceType.Rat) != 0) { //strip away current % dmg buffs then add with rat % + + float percent = 1 + ac.getBonuses().getFloatPercentAll(ModType.Slay, SourceType.Rat); + + minDamage *= percent; + maxDamage *= percent; + } + + errorTrack = 1; + + //subtract stamina + + if (wb == null) + ac.modifyStamina(-0.5f, ac, true); + else { + float stam = wb.getWeight() / 3; + stam = (stam < 1) ? 1 : stam; + ac.modifyStamina(-(stam), ac, true); + } + + ac.cancelOnAttackSwing(); + + errorTrack = 2; + + //set last time this player has attacked something. + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectUUID() != ac.getObjectUUID() && ac.getObjectType() == GameObjectType.PlayerCharacter) { + ac.setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); + ((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); + } else + ac.setTimeStamp("LastCombatMob", System.currentTimeMillis()); + + errorTrack = 3; + + //Get defense for target + + float defense; + + if (target.getObjectType().equals(GameObjectType.Building)) { + + if (BuildingManager.getBuildingFromCache(target.getObjectUUID()) == null) { + ac.setCombatTarget(null); + return; + } + + defense = 0; + + Building building = (Building) target; + + if (building.getParentZone() != null && building.getParentZone().isPlayerCity()) { + + if (System.currentTimeMillis() > building.getTimeStamp("CallForHelp")) { + + building.getTimestamps().put("CallForHelp", System.currentTimeMillis() + 15000); + + for (Mob mob : building.getParentZone().zoneMobSet) { + if (!mob.isPlayerGuard()) + continue; + + if (mob.getCombatTarget() != null) + continue; + + if (mob.getGuild() != null && building.getGuild() != null) + if (!Guild.sameGuild(mob.getGuild().getNation(), building.getGuild().getNation())) + continue; + + if (mob.getLoc().distanceSquared2D(building.getLoc()) > sqr(300)) + continue; + + mob.setCombatTarget(ac); + } + } + } + } else { + AbstractCharacter tar = (AbstractCharacter) target; + defense = tar.getDefenseRating(); + handleRetaliate(tar, ac); //Handle target attacking back if in combat and has no other target + } + + errorTrack = 4; + + //Get hit chance + + int chance; + float dif = atr - defense; + + if (dif > 100) + chance = 94; + else if (dif < -100) + chance = 4; + else + chance = (int) ((0.45 * dif) + 49); + + errorTrack = 5; + + //calculate hit/miss + + int roll = ThreadLocalRandom.current().nextInt(100); + DeferredPowerJob dpj = null; + + if (roll < chance) { + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + updateAttackTimers((PlayerCharacter) ac, target, true); + + boolean skipPassives = false; + PlayerBonuses bonuses = ac.getBonuses(); + + if (bonuses != null && bonuses.getBool(ModType.IgnorePassiveDefense, SourceType.None)) + skipPassives = true; + + AbstractCharacter tarAc = null; + + if (AbstractWorldObject.IsAbstractCharacter(target)) + tarAc = (AbstractCharacter) target; + + errorTrack = 6; + + // Apply Weapon power effect if any. don't try to apply twice if + // dual wielding. Perform after passive test for sync purposes. + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) && (mainHand || wb.isTwoHanded())) { + + dpj = ((PlayerCharacter) ac).getWeaponPower(); + + if (dpj != null) { + + PlayerBonuses bonus = ac.getBonuses(); + float attackRange = getWeaponRange(wb, bonus); + dpj.attack(target, attackRange); + + if (dpj.getPower() != null && (dpj.getPowerToken() == -1851459567 || dpj.getPowerToken() == -1851489518)) + ((PlayerCharacter) ac).setWeaponPower(dpj); + } + } + + //check to apply second backstab. + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) && !mainHand) { + + dpj = ((PlayerCharacter) ac).getWeaponPower(); + + if (dpj != null && dpj.getPower() != null && (dpj.getPowerToken() == -1851459567 || dpj.getPowerToken() == -1851489518)) { + float attackRange = getWeaponRange(wb, bonuses); + dpj.attack(target, attackRange); + } + } + + errorTrack = 7; + + //Hit, check if passive kicked in + + boolean passiveFired = false; + + if (!skipPassives && tarAc != null) { + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + //Handle Block passive + + if (testPassive(ac, tarAc, "Block") && canTestBlock(ac, target)) { + + if (!target.isAlive()) + return; + + sendPassiveDefenseMessage(ac, wb, target, MBServerStatics.COMBAT_SEND_BLOCK, dpj, mainHand); + passiveFired = true; + } + + //Handle Parry passive + + if (!passiveFired) + if (canTestParry(ac, target) && testPassive(ac, tarAc, "Parry")) { + + if (!target.isAlive()) + return; + + sendPassiveDefenseMessage(ac, wb, target, MBServerStatics.COMBAT_SEND_PARRY, dpj, mainHand); + passiveFired = true; + } + + } + + errorTrack = 8; + + //Handle Dodge passive + + if (!passiveFired) + if (testPassive(ac, tarAc, "Dodge")) { + + if (!target.isAlive()) + return; + + sendPassiveDefenseMessage(ac, wb, target, MBServerStatics.COMBAT_SEND_DODGE, dpj, mainHand); + passiveFired = true; + } + } + + //return if passive (Block, Parry, Dodge) fired + + if (passiveFired) + return; + + errorTrack = 9; + + //Hit and no passives + //if target is player, set last attack timestamp + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) + updateAttackTimers((PlayerCharacter) target, ac, false); + + //Get damage Type + + DamageType damageType; + + if (wb != null) + damageType = wb.getDamageType(); + else if (ac.getObjectType().equals(GameObjectType.Mob) && ((Mob) ac).isSiege()) + damageType = DamageType.Siege; + else + damageType = DamageType.Crush; + + errorTrack = 10; + + //Get target resists + + Resists resists = null; + + if (tarAc != null) + resists = tarAc.getResists(); + else if (target.getObjectType().equals(GameObjectType.Building)) + resists = ((Building) target).getResists(); + + //make sure target is not immune to damage type; + + if (resists != null && resists.immuneTo(damageType)) { + sendCombatMessage(ac, target, 0f, wb, dpj, mainHand); + return; + } + + errorTrack = 11; + + //Calculate Damage done + + float damage; + + if (wb != null) + damage = calculateDamage(ac, tarAc, minDamage, maxDamage, damageType, resists); + else + damage = calculateDamage(ac, tarAc, minDamage, maxDamage, damageType, resists); + + float d = 0f; + + errorTrack = 12; + + //Subtract Damage from target's health + + if (tarAc != null) { + + if (tarAc.isSit()) + damage *= 2.5f; //increase damage if sitting + + if (tarAc.getObjectType() == GameObjectType.Mob) { + ac.setHateValue(damage * MBServerStatics.PLAYER_COMBAT_HATE_MODIFIER); + ((Mob) tarAc).handleDirectAggro(ac); + } + + if (tarAc.getHealth() > 0) + d = tarAc.modifyHealth(-damage, ac, false); + + } else if (target.getObjectType().equals(GameObjectType.Building)) { + + if (BuildingManager.getBuildingFromCache(target.getObjectUUID()) == null) { + ac.setCombatTarget(null); + return; + } + + if (target.getHealth() > 0) + d = ((Building) target).modifyHealth(-damage, ac); + } + + errorTrack = 13; + + //Test to see if any damage needs done to weapon or armor + + testItemDamage(ac, target, weapon, wb); + + // if target is dead, we got the killing blow, remove attack timers on our weapons + + if (tarAc != null && !tarAc.isAlive()) + removeAttackTimers(ac); + + //test double death fix + + if (d != 0) + sendCombatMessage(ac, target, damage, wb, dpj, mainHand); //send damage message + + errorTrack = 14; + + //handle procs + + if (weapon != null && tarAc != null && tarAc.isAlive()) { + + ConcurrentHashMap effects = weapon.getEffects(); + + for (Effect eff : effects.values()) { + if (eff == null) + continue; + + HashSet aems = eff.getEffectModifiers(); + + if (aems != null) { + for (AbstractEffectModifier aem : aems) { + + if (!tarAc.isAlive()) + break; + + if (aem instanceof WeaponProcEffectModifier) { + + int procChance = ThreadLocalRandom.current().nextInt(100); + + if (procChance < MBServerStatics.PROC_CHANCE) + ((WeaponProcEffectModifier) aem).applyProc(ac, target); + + } + } + } + } + } + + errorTrack = 15; + + //handle damage shields + + if (ac.isAlive() && tarAc != null && tarAc.isAlive()) + handleDamageShields(ac, tarAc, damage); + + } else { + + // Apply Weapon power effect if any. + // don't try to apply twice if dual wielding. + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) && (mainHand || wb.isTwoHanded())) { + dpj = ((PlayerCharacter) ac).getWeaponPower(); + + if (dpj != null) { + + PowersBase wp = dpj.getPower(); + + if (wp.requiresHitRoll() == false) { + PlayerBonuses bonus = ac.getBonuses(); + float attackRange = getWeaponRange(wb, bonus); + dpj.attack(target, attackRange); + } else + ((PlayerCharacter) ac).setWeaponPower(null); + } + } + + if (target.getObjectType() == GameObjectType.Mob) + ((Mob) target).handleDirectAggro(ac); + + errorTrack = 17; + + //miss, Send miss message + + sendCombatMessage(ac, target, 0f, wb, dpj, mainHand); + + //if attacker is player, set last attack timestamp + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + updateAttackTimers((PlayerCharacter) ac, target, true); + } + + errorTrack = 18; + + //cancel effects that break on attack or attackSwing + ac.cancelOnAttack(); + + } catch (Exception e) { + Logger.error(ac.getName() + ' ' + errorTrack + ' ' + e); + } + } + + public static boolean canTestParry(AbstractCharacter ac, AbstractWorldObject target) { + + if (ac == null || target == null || !AbstractWorldObject.IsAbstractCharacter(target)) + return false; + + AbstractCharacter tar = (AbstractCharacter) target; + + CharacterItemManager acItem = ac.getCharItemManager(); + CharacterItemManager tarItem = tar.getCharItemManager(); + + if (acItem == null || tarItem == null) + return false; + + Item acMain = acItem.getItemFromEquipped(1); + Item acOff = acItem.getItemFromEquipped(2); + Item tarMain = tarItem.getItemFromEquipped(1); + Item tarOff = tarItem.getItemFromEquipped(2); + + return !isRanged(acMain) && !isRanged(acOff) && !isRanged(tarMain) && !isRanged(tarOff); + } + + public static boolean canTestBlock(AbstractCharacter ac, AbstractWorldObject target) { + + if (ac == null || target == null || !AbstractWorldObject.IsAbstractCharacter(target)) + return false; + + AbstractCharacter tar = (AbstractCharacter) target; + + CharacterItemManager acItem = ac.getCharItemManager(); + CharacterItemManager tarItem = tar.getCharItemManager(); + + if (acItem == null || tarItem == null) + return false; + + Item tarOff = tarItem.getItemFromEquipped(2); + + if (tarOff == null) + return false; + + return tarOff.getItemBase().isShield() != false; + } + + private static boolean isRanged(Item item) { + + if (item == null) + return false; + + ItemBase ib = item.getItemBase(); + + if (ib == null) + return false; + + if (ib.getType().equals(ItemType.WEAPON) == false) + return false; + + return ib.getRange() > MBServerStatics.RANGED_WEAPON_RANGE; + + } + + private static float calculateDamage(AbstractCharacter source, AbstractCharacter target, float minDamage, float maxDamage, DamageType damageType, Resists resists) { + + //get range between min and max + + float range = maxDamage - minDamage; + + //Damage is calculated twice to average a more central point + + float damage = ThreadLocalRandom.current().nextFloat() * range; + damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) * .5f; + + //put it back between min and max + + damage += minDamage; + + //calculate resists in if any + + if (resists != null) + return resists.getResistedDamage(source, target, damageType, damage, 0); + else + return damage; + } + + private static void sendPassiveDefenseMessage(AbstractCharacter source, ItemBase wb, AbstractWorldObject target, int passiveType, DeferredPowerJob dpj, boolean mainHand) { + + int swingAnimation = getSwingAnimation(wb, dpj, mainHand); + + if (dpj != null) + if (PowersManager.AnimationOverrides.containsKey(dpj.getAction().getEffectID())) + swingAnimation = PowersManager.AnimationOverrides.get(dpj.getAction().getEffectID()); + + TargetedActionMsg cmm = new TargetedActionMsg(source, swingAnimation, target, passiveType); + DispatchMessage.sendToAllInRange(target, cmm); + + } + + private static void sendCombatMessage(AbstractCharacter source, AbstractWorldObject target, float damage, ItemBase wb, DeferredPowerJob dpj, boolean mainHand) { + + int swingAnimation = getSwingAnimation(wb, dpj, mainHand); + + if (dpj != null) + if (PowersManager.AnimationOverrides.containsKey(dpj.getAction().getEffectID())) + swingAnimation = PowersManager.AnimationOverrides.get(dpj.getAction().getEffectID()); + + if (source.getObjectType() == GameObjectType.PlayerCharacter) + for (Effect eff : source.getEffects().values()) + if (eff.getPower() != null && (eff.getPower().getToken() == 429506943 || eff.getPower().getToken() == 429408639 || eff.getPower().getToken() == 429513599 || eff.getPower().getToken() == 429415295)) + swingAnimation = 0; + + TargetedActionMsg cmm = new TargetedActionMsg(source, target, damage, swingAnimation); + DispatchMessage.sendToAllInRange(target, cmm); + } + + public static int getSwingAnimation(ItemBase wb, DeferredPowerJob dpj, boolean mainHand) { + int token = 0; + + if (dpj != null) + token = (dpj.getPower() != null) ? dpj.getPower().getToken() : 0; + + if (token == 563721004) //kick animation + return 79; + + if (CombatManager.animation != 0) + return CombatManager.animation; + + if (wb == null) + return 75; + + if (mainHand) { + if (wb.getAnimations().size() > 0) { + + int animation; + + int random = ThreadLocalRandom.current().nextInt(wb.getAnimations().size()); + + try { + animation = wb.getAnimations().get(random); + return animation; + } catch (Exception e) { + Logger.error(e.getMessage()); + return wb.getAnimations().get(0); + } + + } else if (wb.getOffHandAnimations().size() > 0) { + + int animation; + int random = ThreadLocalRandom.current().nextInt(wb.getOffHandAnimations().size()); + + try { + animation = wb.getOffHandAnimations().get(random); + return animation; + } catch (Exception e) { + Logger.error(e.getMessage()); + return wb.getOffHandAnimations().get(0); + } + } + } else { + if (wb.getOffHandAnimations().size() > 0) { + int animation; + int random = ThreadLocalRandom.current().nextInt(wb.getOffHandAnimations().size()); + + try { + animation = wb.getOffHandAnimations().get(random); + return animation; + } catch (Exception e) { + Logger.error(e.getMessage()); + return wb.getOffHandAnimations().get(0); + + } + } else if (wb.getAnimations().size() > 0) { + + int animation; + int random = ThreadLocalRandom.current().nextInt(wb.getAnimations().size()); + + try { + animation = wb.getAnimations().get(random); + return animation; + } catch (Exception e) { + Logger.error(e.getMessage()); + return wb.getAnimations().get(0); + + } + + } + } + + + String required = wb.getSkillRequired(); + String mastery = wb.getMastery(); + + if (required.equals("Unarmed Combat")) + return 75; + else if (required.equals("Sword")) { + + if (wb.isTwoHanded()) + return 105; + else + return 98; + + } else if (required.equals("Staff") || required.equals("Pole Arm")) { + return 85; + } else if (required.equals("Spear")) { + return 92; + } else if (required.equals("Hammer") || required.equals("Axe")) { + if (wb.isTwoHanded()) { + return 105; + } else if (mastery.equals("Throwing")) { + return 115; + } else { + return 100; + } + } else if (required.equals("Dagger")) { + if (mastery.equals("Throwing")) { + return 117; + } else { + return 81; + } + } else if (required.equals("Crossbow")) { + return 110; + } else if (required.equals("Bow")) { + return 109; + } else if (wb.isTwoHanded()) { + return 105; + } else { + return 100; + } + } + + private static boolean testPassive(AbstractCharacter source, AbstractCharacter target, String type) { + + float chance = target.getPassiveChance(type, source.getLevel(), true); + + if (chance == 0f) + return false; + + //max 75% chance of passive to fire + + if (chance > 75f) + chance = 75f; + + int roll = ThreadLocalRandom.current().nextInt(100); + + return roll < chance; + + } + + private static void updateAttackTimers(PlayerCharacter pc, AbstractWorldObject target, boolean attack) { + + //Set Attack Timers + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) + pc.setLastPlayerAttackTime(); + } + + public static float getWeaponRange(ItemBase weapon, PlayerBonuses bonus) { + + float rangeMod = 1.0f; + + if (weapon == null) + return 0f; + + if (bonus != null) + rangeMod += bonus.getFloatPercentAll(ModType.WeaponRange, SourceType.None); + + return weapon.getRange() * rangeMod; + } + + public static void toggleCombat(ToggleCombatMsg msg, ClientConnection origin) { + toggleCombat(msg.toggleCombat(), origin); + } + + public static void toggleCombat(SetCombatModeMsg msg, ClientConnection origin) { + toggleCombat(msg.getToggle(), origin); + } + + private static void toggleCombat(boolean toggle, ClientConnection origin) { + + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + + if (pc == null) + return; + + pc.setCombat(toggle); + + if (!toggle) // toggle is move it to false so clear combat target + pc.setCombatTarget(null); //clear last combat target + + UpdateStateMsg rwss = new UpdateStateMsg(); + rwss.setPlayer(pc); + DispatchMessage.dispatchMsgToInterestArea(pc, rwss, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + } + + private static void toggleSit(boolean toggle, ClientConnection origin) { + + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + + if (pc == null) + return; + + pc.setSit(toggle); + + UpdateStateMsg rwss = new UpdateStateMsg(); + rwss.setPlayer(pc); + DispatchMessage.dispatchMsgToInterestArea(pc, rwss, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + + public static boolean NotInRange(AbstractCharacter ac, AbstractWorldObject target, float range) { + + Vector3fImmutable sl = ac.getLoc(); + Vector3fImmutable tl = target.getLoc(); + + //add Hitbox to range. + + range += (calcHitBox(ac) + calcHitBox(target)); + + float magnitudeSquared = tl.distanceSquared(sl); + + return magnitudeSquared > range * range; + + } + + //Called when character takes damage. + public static void handleRetaliate(AbstractCharacter tarAc, AbstractCharacter ac) { + + if (ac == null || tarAc == null) + return; + + if (ac.equals(tarAc)) + return; + + if (tarAc.isMoving() && tarAc.getObjectType().equals(GameObjectType.PlayerCharacter)) + return; + + if (!tarAc.isAlive() || !ac.isAlive()) + return; + + boolean isCombat = tarAc.isCombat(); + + //If target in combat and has no target, then attack back + + AbstractWorldObject awoCombTar = tarAc.getCombatTarget(); + + if ((tarAc.isCombat() && awoCombTar == null) || (isCombat && awoCombTar != null && (!awoCombTar.isAlive() || tarAc.isCombat() && NotInRange(tarAc, awoCombTar, tarAc.getRange()))) || (tarAc != null && tarAc.getObjectType() == GameObjectType.Mob && ((Mob) tarAc).isSiege())) + if (tarAc.getObjectType().equals(GameObjectType.PlayerCharacter)) { // we are in combat with no valid target + + PlayerCharacter pc = (PlayerCharacter) tarAc; + tarAc.setCombatTarget(ac); + pc.setLastTarget(ac.getObjectType(), ac.getObjectUUID()); + + if (tarAc.getTimers() != null) + if (!tarAc.getTimers().containsKey("Attack" + MBServerStatics.SLOT_MAINHAND)) + CombatManager.AttackTarget((PlayerCharacter) tarAc, tarAc.getCombatTarget()); + } + + //Handle pet retaliate if assist is on and pet doesn't have a target. + + if (tarAc.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + Mob pet = ((PlayerCharacter) tarAc).getPet(); + + if (pet != null && pet.assist && pet.getCombatTarget() == null) + pet.setCombatTarget(ac); + } + + //Handle Mob Retaliate. + + if (tarAc.getObjectType() == GameObjectType.Mob) { + + Mob retaliater = (Mob) tarAc; + + if (retaliater.getCombatTarget() != null && !retaliater.isSiege()) + return; + + if (ac.getObjectType() == GameObjectType.Mob && retaliater.isSiege()) + return; + + retaliater.setCombatTarget(ac); + + } + } + + public static void handleDamageShields(AbstractCharacter ac, AbstractCharacter target, float damage) { + + if (ac == null || target == null) + return; + + PlayerBonuses bonuses = target.getBonuses(); + + if (bonuses != null) { + + ConcurrentHashMap damageShields = bonuses.getDamageShields(); + float total = 0; + + for (DamageShield ds : damageShields.values()) { + + //get amount to damage back + + float amount; + + if (ds.usePercent()) + amount = damage * ds.getAmount() / 100; + else + amount = ds.getAmount(); + + //get resisted damage for damagetype + + Resists resists = ac.getResists(); + + if (resists != null) + amount = resists.getResistedDamage(target, ac, ds.getDamageType(), amount, 0); + + total += amount; + } + + if (total > 0) { + + //apply Damage back + + ac.modifyHealth(-total, target, true); + + TargetedActionMsg cmm = new TargetedActionMsg(ac, ac, total, 0); + DispatchMessage.sendToAllInRange(target, cmm); + + } + } + } + + public static float calcHitBox(AbstractWorldObject ac) { + + //TODO Figure out how Str Affects HitBox + + float hitBox = 1; + + switch (ac.getObjectType()) { + case PlayerCharacter: + PlayerCharacter pc = (PlayerCharacter) ac; + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) { + Logger.info("Hit box radius for " + pc.getFirstName() + " is " + ((int) pc.statStrBase / 20f)); + } + hitBox = 1.5f + (int) ((PlayerCharacter) ac).statStrBase / 20f; + break; + + case Mob: + Mob mob = (Mob) ac; + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) + Logger.info("Hit box radius for " + mob.getFirstName() + + " is " + ((Mob) ac).getMobBase().getHitBoxRadius()); + + hitBox = ((Mob) ac).getMobBase().getHitBoxRadius(); + break; + case Building: + Building building = (Building) ac; + if (building.getBlueprint() == null) + return 32; + hitBox = Math.max(building.getBlueprint().getBuildingGroup().getExtents().x, + building.getBlueprint().getBuildingGroup().getExtents().y); + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) + Logger.info("Hit box radius for " + building.getName() + " is " + hitBox); + break; + + } + return hitBox; + } + + private static void testItemDamage(AbstractCharacter ac, AbstractWorldObject awo, Item weapon, ItemBase wb) { + + if (ac == null) + return; + + //get chance to damage + + int chance = 4500; + + if (wb != null) + if (wb.isGlass()) //glass used weighted so fast weapons don't break faster + chance = 9000 / wb.getWeight(); + + //test damaging attackers weapon + + int takeDamage = ThreadLocalRandom.current().nextInt(chance); + + if (takeDamage == 0 && wb != null && (ac.getObjectType().equals(GameObjectType.PlayerCharacter))) + ac.getCharItemManager().damageItem(weapon, 1); + + + //test damaging targets gear + + takeDamage = ThreadLocalRandom.current().nextInt(chance); + + if (takeDamage == 0 && awo != null && (awo.getObjectType().equals(GameObjectType.PlayerCharacter))) + ((AbstractCharacter) awo).getCharItemManager().damageRandomArmor(1); + } + +} diff --git a/src/engine/gameManager/ConfigManager.java b/src/engine/gameManager/ConfigManager.java new file mode 100644 index 00000000..7541a4e1 --- /dev/null +++ b/src/engine/gameManager/ConfigManager.java @@ -0,0 +1,176 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +/* This enumeration implements Magicbane's configuration data which + is loaded from environment variables. + */ + +import engine.Enum; +import engine.net.NetMsgHandler; +import engine.server.login.LoginServer; +import engine.server.world.WorldServer; +import org.pmw.tinylog.Logger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public enum ConfigManager { + + MB_BIND_ADDR, + MB_EXTERNAL_ADDR, + // Database connection config + + MB_DATABASE_ADDRESS, + MB_DATABASE_PORT, + MB_DATABASE_NAME, + MB_DATABASE_USER, + MB_DATABASE_PASS, + + // Data warehouse remote connection + + MB_WAREHOUSE_ADDR, + MB_WAREHOUSE_USER, + MB_WAREHOUSE_PASS, + + // Login server config + + MB_LOGIN_PORT, + MB_LOGIN_AUTOREG, + MB_LOGIN_FNAME_REGEX, + + MB_MAJOR_VER, + MB_MINOR_VER, + + // Worldserver configuration + + MB_WORLD_NAME, + MB_WORLD_MAPID, + MB_WORLD_REALMMAP, + + MB_WORLD_PORT, + MB_WORLD_ACCESS_LVL, + MB_WORLD_WAREHOUSE_PUSH, + MB_WORLD_MAINTENANCE, + MB_WORLD_GREETING, + MB_WORLD_KEYCLONE_MAX, + MB_USE_RUINS, + + // Mobile AI modifiers + MB_AI_CAST_FREQUENCY, + MB_AI_AGGRO_RANGE, + + //drop rates + MB_NORMAL_EXP_RATE, + MB_NORMAL_DROP_RATE, + MB_NORMAL_GOLD_RATE, + MB_HOTZONE_EXP_RATE, + MB_HOTZONE_DROP_RATE, + MB_HOTZONE_GOLD_RATE, + MB_HOTZONE_DURATION, + + MB_HOTZONE_MIN_LEVEL, + + MB_PRODUCTION_RATE, + + // MagicBot configuration. + + MB_MAGICBOT_SERVERID, + MB_MAGICBOT_BOTTOKEN, + MB_MAGICBOT_ROLEID, + MB_MAGICBOT_ANNOUNCE, + MB_MAGICBOT_SEPTIC, + MB_MAGICBOT_CHANGELOG, + MB_MAGICBOT_POLITICAL, + MB_MAGICBOT_GENERAL, + MB_MAGICBOT_FORTOFIX, + MB_MAGICBOT_RECRUIT, + MB_MAGICBOT_MAGICBOX, + MB_MAGICBOT_ADMINLOG; + + // Map to hold our config pulled in from the environment + // We also use the config to point to the current message pump + // and determine the server type at runtime. + + public static final String DEFAULT_DATA_DIR = "mb.data/"; + public static Map configMap = new HashMap(System.getenv()); + public static Enum.ServerType serverType = Enum.ServerType.NONE; + public static NetMsgHandler handler; + public static WorldServer worldServer; + public static LoginServer loginServer; + public static Map regex = new HashMap<>(); + + public static String currentRepoBranch = ""; + + // Called at bootstrap: ensures that all config values are loaded. + + public static boolean init() { + + Logger.info("Loading config from environment."); + + for (ConfigManager configSetting : ConfigManager.values()) + if (configMap.containsKey(configSetting.name())) + Logger.info(configSetting.name() + ":" + configSetting.getValue()); + else { + Logger.error("Missing Config: " + configSetting.name()); + Logger.error("This codebase requires >= MagicBox v1.5.1"); + Logger.error("docker pull magicbane/magicbox:latest"); + return false; + } + + Logger.info("Determine current Repo branch"); + + File file = new File("mbbranch.sh"); + + if (file.exists() && !file.isDirectory()) { + + String[] command = {"./mbbranch.sh"}; + + try { + + Process process = Runtime.getRuntime().exec(command); + BufferedReader reader = new BufferedReader(new InputStreamReader( + process.getInputStream())); + String string; + + while (true) { + if ((string = reader.readLine()) == null) + break; + currentRepoBranch += string; + } + + Logger.info(currentRepoBranch); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + // compile regex here + + Logger.info("Compiling regex"); + + regex.put(MB_LOGIN_FNAME_REGEX, Pattern.compile(MB_LOGIN_FNAME_REGEX.getValue())); + return true; + } + + // Get the value associated with this enumeration + + public String getValue() { + return configMap.get(this.name()); + } + + public void setValue(String value) { + configMap.put(this.name(), value); + } +} diff --git a/src/engine/gameManager/DbManager.java b/src/engine/gameManager/DbManager.java new file mode 100644 index 00000000..92917a35 --- /dev/null +++ b/src/engine/gameManager/DbManager.java @@ -0,0 +1,328 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.db.handlers.*; +import engine.objects.*; +import engine.server.MBServerStatics; +import engine.util.Hasher; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.EnumMap; +import java.util.concurrent.ConcurrentHashMap; + +public enum DbManager { + DBMANAGER; + + public static final dbAccountHandler AccountQueries = new dbAccountHandler(); + public static final dbBaneHandler BaneQueries = new dbBaneHandler(); + + //Local Object Caching + public static final dbBaseClassHandler BaseClassQueries = new dbBaseClassHandler(); + public static final dbBuildingHandler BuildingQueries = new dbBuildingHandler(); + public static final dbBuildingLocationHandler BuildingLocationQueries = new dbBuildingLocationHandler(); + public static final dbCharacterPowerHandler CharacterPowerQueries = new dbCharacterPowerHandler(); + public static final dbCharacterRuneHandler CharacterRuneQueries = new dbCharacterRuneHandler(); + public static final dbCharacterSkillHandler CharacterSkillQueries = new dbCharacterSkillHandler(); + public static final dbCityHandler CityQueries = new dbCityHandler(); + public static final dbContractHandler ContractQueries = new dbContractHandler(); + public static final dbWarehouseHandler WarehouseQueries = new dbWarehouseHandler(); + + // Omg refactor this out, somebody! + public static final dbCSSessionHandler CSSessionQueries = new dbCSSessionHandler(); + public static final dbEnchantmentHandler EnchantmentQueries = new dbEnchantmentHandler(); + public static final dbEffectsResourceCostHandler EffectsResourceCostsQueries = new dbEffectsResourceCostHandler(); + public static final dbGuildHandler GuildQueries = new dbGuildHandler(); + public static final dbItemHandler ItemQueries = new dbItemHandler(); + public static final dbItemBaseHandler ItemBaseQueries = new dbItemBaseHandler(); + public static final dbKitHandler KitQueries = new dbKitHandler(); + public static final dbLootHandler LootQueries = new dbLootHandler(); + public static final dbMenuHandler MenuQueries = new dbMenuHandler(); + public static final dbMineHandler MineQueries = new dbMineHandler(); + public static final dbMobHandler MobQueries = new dbMobHandler(); + public static final dbMobBaseHandler MobBaseQueries = new dbMobBaseHandler(); + public static final dbNPCHandler NPCQueries = new dbNPCHandler(); + public static final dbPlayerCharacterHandler PlayerCharacterQueries = new dbPlayerCharacterHandler(); + public static final dbPromotionClassHandler PromotionQueries = new dbPromotionClassHandler(); + public static final dbRaceHandler RaceQueries = new dbRaceHandler(); + public static final dbResistHandler ResistQueries = new dbResistHandler(); + public static final dbRuneBaseAttributeHandler RuneBaseAttributeQueries = new dbRuneBaseAttributeHandler(); + public static final dbRuneBaseEffectHandler RuneBaseEffectQueries = new dbRuneBaseEffectHandler(); + public static final dbRuneBaseHandler RuneBaseQueries = new dbRuneBaseHandler(); + public static final dbSkillBaseHandler SkillsBaseQueries = new dbSkillBaseHandler(); + public static final dbSkillReqHandler SkillReqQueries = new dbSkillReqHandler(); + public static final dbVendorDialogHandler VendorDialogQueries = new dbVendorDialogHandler(); + public static final dbZoneHandler ZoneQueries = new dbZoneHandler(); + public static final dbRealmHandler RealmQueries = new dbRealmHandler(); + public static final dbBlueprintHandler BlueprintQueries = new dbBlueprintHandler(); + public static final dbBoonHandler BoonQueries = new dbBoonHandler(); + public static final dbShrineHandler ShrineQueries = new dbShrineHandler(); + public static final dbHeightMapHandler HeightMapQueries = new dbHeightMapHandler(); + public static final dbRunegateHandler RunegateQueries = new dbRunegateHandler(); + + public static final dbPowerHandler PowerQueries = new dbPowerHandler(); + public static final dbPetitionHandler PetitionQueries = new dbPetitionHandler(); + private static final EnumMap> objectCache = new EnumMap<>(GameObjectType.class); + public static Hasher hasher; + private static HikariDataSource connectionPool = null; + + public static AbstractGameObject getObject(GameObjectType objectType, int objectUUID) { + + AbstractGameObject outObject = null; + + switch (objectType) { + case PlayerCharacter: + outObject = PlayerCharacter.getPlayerCharacter(objectUUID); + break; + case NPC: + outObject = NPC.getNPC(objectUUID); + break; + case Mob: + outObject = Mob.getFromCache(objectUUID); + break; + case Building: + outObject = BuildingManager.getBuilding(objectUUID); + break; + case Guild: + outObject = Guild.getGuild(objectUUID); + break; + case Item: + outObject = Item.getFromCache(objectUUID); + break; + case MobLoot: + outObject = MobLoot.getFromCache(objectUUID); + break; + case City: + outObject = City.getCity(objectUUID); + break; + default: + Logger.error("Attempt to retrieve nonexistant " + objectType + + " from object cache."); + break; + + } + + return outObject; + } + + public static boolean inCache(GameObjectType gameObjectType, int uuid) { + + if (objectCache.get(gameObjectType) == null) + return false; + + return (objectCache.get(gameObjectType).containsKey(uuid)); + + } + + public static AbstractGameObject getFromCache(GameObjectType gameObjectType, int uuid) { + + if (objectCache.get(gameObjectType) == null) + return null; + + return objectCache.get(gameObjectType).get(uuid); + + } + + public static void removeFromCache(GameObjectType gameObjectType, int uuid) { + + AbstractGameObject abstractGameObject; + + if (objectCache.get(gameObjectType) == null) + return; + + abstractGameObject = objectCache.get(gameObjectType).get(uuid); + + if (abstractGameObject == null) + return; + + removeFromCache(abstractGameObject); + + } + + public static void removeFromCache(AbstractGameObject abstractGameObject) { + + if (abstractGameObject == null) + return; + + if (objectCache.get(abstractGameObject.getObjectType()) == null) + return; + + // Remove object from game cache + + objectCache.get(abstractGameObject.getObjectType()).remove(abstractGameObject.getObjectUUID()); + + // Release bounds as we're dispensing with this object. + + if (abstractGameObject instanceof AbstractWorldObject) { + AbstractWorldObject abstractWorldObject = (AbstractWorldObject) abstractGameObject; + + if (abstractWorldObject.getBounds() != null) { + abstractWorldObject.getBounds().release(); + abstractWorldObject.setBounds(null); + } + } + + } + + public static boolean addToCache(AbstractGameObject gameObject) { + + boolean isWorldServer = ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER); + + if (!isWorldServer) { + if (MBServerStatics.SKIP_CACHE_LOGIN) + return true; + if (MBServerStatics.SKIP_CACHE_LOGIN_PLAYER + && (gameObject.getObjectType() == GameObjectType.PlayerCharacter)) + return true; + if (MBServerStatics.SKIP_CACHE_LOGIN_ITEM && + (gameObject.getObjectType() == GameObjectType.Item)) + return true; + } + + // First time this object type has been cached. Create the hashmap. + + if (objectCache.get(gameObject.getObjectType()) == null) { + + int initialCapacity; + + // Provide initial sizing hints + + switch (gameObject.getObjectType()) { + case Building: + initialCapacity = 46900; + break; + case Mob: + initialCapacity = 11700; + break; + case NPC: + initialCapacity = 900; + break; + case Zone: + initialCapacity = 1070; + break; + case Account: + initialCapacity = 10000; + break; + case Guild: + initialCapacity = 100; + break; + case ItemContainer: + initialCapacity = 100; + break; + case Item: + initialCapacity = 1000; + break; + case MobLoot: + initialCapacity = 10000; + break; + case PlayerCharacter: + initialCapacity = 100; + break; + default: + initialCapacity = 100; // Lookup api default should be ok for small maps + break; + } + objectCache.put(gameObject.getObjectType(), new ConcurrentHashMap<>(initialCapacity)); + } + + // Add the object to the cache. This will overwrite the current map entry. + + objectCache.get(gameObject.getObjectType()).put(gameObject.getObjectUUID(), gameObject); + + return true; + } + + public static java.util.Collection getList(GameObjectType gameObjectType) { + + if (objectCache.get(gameObjectType) == null) + return null; + + return objectCache.get(gameObjectType).values(); + } + + public static PreparedStatement prepareStatement(String sql) throws SQLException { + return getConnection().prepareStatement(sql, 1); + } + + public static ConcurrentHashMap getMap( + GameObjectType gameObjectType) { + + if (objectCache.get(gameObjectType) == null) + return null; + + return objectCache.get(gameObjectType); + + } + + public static void printCacheCount(PlayerCharacter pc) { + ChatManager.chatSystemInfo(pc, "Cache Lists"); + + for (GameObjectType gameObjectType : GameObjectType.values()) { + + if (objectCache.get(gameObjectType) == null) + continue; + + String ret = gameObjectType.name() + ": " + objectCache.get(gameObjectType).size(); + ChatManager.chatSystemInfo(pc, ret + '\n'); + } + } + + /** + * @return the conn + */ + //XXX I think we have a severe resource leak here! No one is putting the connections back! + public static Connection getConnection() { + try { + return DbManager.connectionPool.getConnection(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public static void configureConnectionPool() { + + HikariConfig config = new HikariConfig(); + + // Magicbane requires at least 15 db connections min to boot. + + int connectionCount = Math.max(15, Runtime.getRuntime().availableProcessors() * 2) + 1; + config.setMaximumPoolSize(connectionCount); + + config.setJdbcUrl("jdbc:mysql://" + ConfigManager.MB_DATABASE_ADDRESS.getValue() + + ":" + ConfigManager.MB_DATABASE_PORT.getValue() + "/" + + ConfigManager.MB_DATABASE_NAME.getValue()); + config.setUsername(ConfigManager.MB_DATABASE_USER.getValue()); + config.setPassword(ConfigManager.MB_DATABASE_PASS.getValue()); + + // Must be set lower than SQL server connection lifetime! + + config.addDataSourceProperty("maxLifetime", "3600000"); + + config.addDataSourceProperty("characterEncoding", "utf8"); + + config.addDataSourceProperty("useServerPrepStmts", "true"); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "500"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + + config.addDataSourceProperty("leakDetectionThreshold", "5000"); + config.addDataSourceProperty("cacheServerConfiguration", "true"); + + connectionPool = new HikariDataSource(config); // setup the connection pool + + Logger.info("Database configured with " + connectionCount + " connections"); + } +} diff --git a/src/engine/gameManager/DevCmdManager.java b/src/engine/gameManager/DevCmdManager.java new file mode 100644 index 00000000..47ae6cda --- /dev/null +++ b/src/engine/gameManager/DevCmdManager.java @@ -0,0 +1,219 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum; +import engine.devcmd.AbstractDevCmd; +import engine.devcmd.cmds.*; +import engine.objects.AbstractGameObject; +import engine.objects.Account; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +public enum DevCmdManager { + DEV_CMD_MANAGER; + + public static ConcurrentHashMap devCmds; + + DevCmdManager() { + init(); + } + + public static void init() { + DevCmdManager.devCmds = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + DevCmdManager.registerCommands(); + } + + /** + * + */ + private static void registerCommands() { + + // Player + DevCmdManager.registerDevCmd(new DistanceCmd()); + DevCmdManager.registerDevCmd(new HelpCmd()); + DevCmdManager.registerDevCmd(new GetZoneCmd()); + DevCmdManager.registerDevCmd(new ZoneSetCmd()); + DevCmdManager.registerDevCmd(new PrintBankCmd()); + DevCmdManager.registerDevCmd(new PrintEquipCmd()); + DevCmdManager.registerDevCmd(new PrintInventoryCmd()); + DevCmdManager.registerDevCmd(new PrintVaultCmd()); + DevCmdManager.registerDevCmd(new PrintStatsCmd()); + DevCmdManager.registerDevCmd(new PrintSkillsCmd()); + DevCmdManager.registerDevCmd(new PrintPowersCmd()); + DevCmdManager.registerDevCmd(new PrintBonusesCmd()); + DevCmdManager.registerDevCmd(new PrintResistsCmd()); + DevCmdManager.registerDevCmd(new PrintLocationCmd()); + DevCmdManager.registerDevCmd(new InfoCmd()); + DevCmdManager.registerDevCmd(new aiInfoCmd()); + DevCmdManager.registerDevCmd(new SimulateBootyCmd()); + DevCmdManager.registerDevCmd(new GetHeightCmd()); + + // Tester + DevCmdManager.registerDevCmd(new JumpCmd()); + DevCmdManager.registerDevCmd(new GotoCmd()); + DevCmdManager.registerDevCmd(new SummonCmd()); + DevCmdManager.registerDevCmd(new SetHealthCmd()); + DevCmdManager.registerDevCmd(new SetManaCmd()); + DevCmdManager.registerDevCmd(new SetStaminaCmd()); + DevCmdManager.registerDevCmd(new FindBuildingsCmd()); + DevCmdManager.registerDevCmd(new TeleportModeCmd()); + DevCmdManager.registerDevCmd(new SetLevelCmd()); + DevCmdManager.registerDevCmd(new SetBaseClassCmd()); + DevCmdManager.registerDevCmd(new SetPromotionClassCmd()); + DevCmdManager.registerDevCmd(new EffectCmd()); + DevCmdManager.registerDevCmd(new SetRuneCmd()); + DevCmdManager.registerDevCmd(new GetOffsetCmd()); + DevCmdManager.registerDevCmd(new DebugCmd()); + DevCmdManager.registerDevCmd(new AddGoldCmd()); + DevCmdManager.registerDevCmd(new ZoneInfoCmd()); + DevCmdManager.registerDevCmd(new DebugMeleeSyncCmd()); + DevCmdManager.registerDevCmd(new HotzoneCmd()); + DevCmdManager.registerDevCmd(new MineActiveCmd()); + // Dev + DevCmdManager.registerDevCmd(new ApplyStatModCmd()); + DevCmdManager.registerDevCmd(new AddBuildingCmd()); + DevCmdManager.registerDevCmd(new AddNPCCmd()); + DevCmdManager.registerDevCmd(new AddMobCmd()); + DevCmdManager.registerDevCmd(new RemoveObjectCmd()); + DevCmdManager.registerDevCmd(new RotateCmd()); + DevCmdManager.registerDevCmd(new FlashMsgCmd()); + DevCmdManager.registerDevCmd(new SysMsgCmd()); + DevCmdManager.registerDevCmd(new GetBankCmd()); + DevCmdManager.registerDevCmd(new GetVaultCmd()); + DevCmdManager.registerDevCmd(new CombatMessageCmd()); + DevCmdManager.registerDevCmd(new RenameCmd()); + DevCmdManager.registerDevCmd(new CreateItemCmd()); + DevCmdManager.registerDevCmd(new GetMemoryCmd()); + DevCmdManager.registerDevCmd(new SetRankCmd()); + DevCmdManager.registerDevCmd(new MakeBaneCmd()); + DevCmdManager.registerDevCmd(new RemoveBaneCmd()); + DevCmdManager.registerDevCmd(new SetBaneActiveCmd()); + DevCmdManager.registerDevCmd(new SetAdminRuneCmd()); + DevCmdManager.registerDevCmd(new SetInvulCmd()); + DevCmdManager.registerDevCmd(new MakeItemCmd()); + DevCmdManager.registerDevCmd(new EnchantCmd()); + DevCmdManager.registerDevCmd(new SetSubRaceCmd()); + // Admin + DevCmdManager.registerDevCmd(new GetCacheCountCmd()); + DevCmdManager.registerDevCmd(new GetRuneDropRateCmd()); + DevCmdManager.registerDevCmd(new DecachePlayerCmd()); + DevCmdManager.registerDevCmd(new AuditMobsCmd()); + DevCmdManager.registerDevCmd(new ChangeNameCmd()); + DevCmdManager.registerDevCmd(new SetGuildCmd()); + DevCmdManager.registerDevCmd(new SetOwnerCmd()); + DevCmdManager.registerDevCmd(new NetDebugCmd()); + DevCmdManager.registerDevCmd(new SqlDebugCmd()); + DevCmdManager.registerDevCmd(new PullCmd()); + DevCmdManager.registerDevCmd(new PurgeObjectsCmd()); + DevCmdManager.registerDevCmd(new SplatMobCmd()); + DevCmdManager.registerDevCmd(new SlotNpcCmd()); + DevCmdManager.registerDevCmd(new GateInfoCmd()); + DevCmdManager.registerDevCmd(new ShowOffsetCmd()); + DevCmdManager.registerDevCmd(new RealmInfoCmd()); + DevCmdManager.registerDevCmd(new RebootCmd()); + DevCmdManager.registerDevCmd(new SetMineTypeCmd()); + DevCmdManager.registerDevCmd(new SetMineExpansion()); + DevCmdManager.registerDevCmd(new SetForceRenameCityCmd()); + DevCmdManager.registerDevCmd(new GotoObj()); + DevCmdManager.registerDevCmd(new convertLoc()); + DevCmdManager.registerDevCmd(new AuditHeightMapCmd()); + DevCmdManager.registerDevCmd(new UnloadFurnitureCmd()); + DevCmdManager.registerDevCmd(new SetNpcEquipSetCmd()); + DevCmdManager.registerDevCmd(new SetBuildingAltitudeCmd()); + DevCmdManager.registerDevCmd(new ResetLevelCmd()); + DevCmdManager.registerDevCmd(new SetNpcNameCmd()); + DevCmdManager.registerDevCmd(new SetNpcMobbaseCmd()); + DevCmdManager.registerDevCmd(new DespawnCmd()); + DevCmdManager.registerDevCmd(new BoundsCmd()); + DevCmdManager.registerDevCmd(new GotoBoundsCmd()); + DevCmdManager.registerDevCmd(new RegionCmd()); + DevCmdManager.registerDevCmd(new SetMaintCmd()); + DevCmdManager.registerDevCmd(new ApplyBonusCmd()); + DevCmdManager.registerDevCmd(new AuditFailedItemsCmd()); + DevCmdManager.registerDevCmd(new SlotTestCmd()); + + } + + private static void registerDevCmd(AbstractDevCmd cmd) { + ArrayList cmdStrings = cmd.getCmdStrings(); + for (String cmdString : cmdStrings) { + DevCmdManager.devCmds.put(cmdString, cmd); + } + } + + public static AbstractDevCmd getDevCmd(String cmd) { + String lowercase = cmd.toLowerCase(); + return DevCmdManager.devCmds.get(lowercase); + } + + public static boolean handleDevCmd(PlayerCharacter pcSender, String cmd, + String argString, AbstractGameObject target) { + + if (pcSender == null) { + return false; + } + + Account a = SessionManager.getAccount(pcSender); + + if (a == null) { + return false; + } + + AbstractDevCmd adc = DevCmdManager.getDevCmd(cmd); + + if (adc == null) { + return false; + } + + //kill any commands not available to everyone on production server + //only admin level can run dev commands on production + + if (a.status.equals(Enum.AccountStatus.ADMIN) == false) { + Logger.info("Account " + a.getUname() + "attempted to use dev command " + cmd); + return false; + } + + // TODO add a job here to separate calling thread form executing thread? + // Log + + String accName = a.getUname(); + String pcName = pcSender.getCombinedName(); + String logString = pcName + '(' + accName + + ") '"; + logString += cmd + ' ' + argString + '\''; + Logger.info(logString); + DbManager.AccountQueries.WRITE_ADMIN_LOG(pcName, "COMMAND: " + cmd + " " + argString); + + // execute command; + try { + adc.doCmd(pcSender, argString, target); + } catch (Exception e) { + Logger.error(e.toString()); + e.printStackTrace(); + } + + return true; + } + + public static String getCmdsForAccessLevel() { + String out = ""; + + for (Entry e : DevCmdManager.devCmds.entrySet()) + out += e.getKey() + ", "; + + return out; + } + +} diff --git a/src/engine/gameManager/GroupManager.java b/src/engine/gameManager/GroupManager.java new file mode 100644 index 00000000..fad20eda --- /dev/null +++ b/src/engine/gameManager/GroupManager.java @@ -0,0 +1,390 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.UpdateGoldMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public enum GroupManager { + + GROUPMANAGER; + + // used for quick lookup of groups by the ID of the group sent in the msg + private static final ConcurrentHashMap groupsByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + + // an index for playercharacters to group membership + private static final ConcurrentHashMap groupsByAC = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + private static int groupCount = 0; + + /* + * Class Implementation + */ + public static void removeFromGroups(AbstractCharacter ac) { + Group gr = null; + + synchronized (GroupManager.groupsByAC) { + gr = GroupManager.groupsByAC.remove(ac); + } + + } + + public static void LeaveGroup(ClientConnection origin) throws MsgSendException { + PlayerCharacter source = SessionManager.getPlayerCharacter(origin); + LeaveGroup(source); + } + + public static void LeaveGroup(PlayerCharacter source) throws MsgSendException { + + if (source == null) + return; + + Group group = GroupManager.groupsByAC.get(source); + + if (group == null) // source is not in a group + return; + + // Cleanup group window for player quiting + GroupUpdateMsg groupUpdateMsg = new GroupUpdateMsg(); + groupUpdateMsg.setGroup(group); + groupUpdateMsg.setPlayer(source); + groupUpdateMsg.setMessageType(3); + + Set groupMembers = group.getMembers(); + + for (PlayerCharacter groupMember : groupMembers) { + + if (groupMember == null) + continue; + + groupUpdateMsg = new GroupUpdateMsg(); + groupUpdateMsg.setGroup(group); + groupUpdateMsg.setPlayer(source); + groupUpdateMsg.setMessageType(3); + groupUpdateMsg.setPlayer(groupMember); + Dispatch dispatch = Dispatch.borrow(source, groupUpdateMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + } + + // Remove from group + int size = group.removeGroupMember(source); + // remove from the group -> ac mapping list + GroupManager.groupsByAC.remove(source); + + if (size == 0) { + GroupManager.deleteGroup(group); + return; // group empty so cleanup group and we're done + } + + // set new group lead if needed + if (group.getGroupLead() == source) { + PlayerCharacter newLead = group.getMembers().iterator().next(); + group.setGroupLead(newLead.getObjectUUID()); + groupUpdateMsg = new GroupUpdateMsg(); + groupUpdateMsg.setGroup(group); + groupUpdateMsg.setPlayer(newLead); + groupUpdateMsg.addPlayer(source); + groupUpdateMsg.setMessageType(2); + group.sendUpdate(groupUpdateMsg); + + // Disable Formation + newLead.setFollow(false); + groupUpdateMsg = new GroupUpdateMsg(); + groupUpdateMsg.setGroup(group); + groupUpdateMsg.setPlayer(newLead); + groupUpdateMsg.setMessageType(8); + group.sendUpdate(groupUpdateMsg); + } + + //send message to group + PlayerCharacter pc = group.getGroupLead(); + //Fixed + String text = source.getFirstName() + " has left the group."; + ChatManager.chatGroupInfo(pc, text); + + // cleanup other group members screens + groupUpdateMsg = new GroupUpdateMsg(); + groupUpdateMsg.setGroup(group); + groupUpdateMsg.setPlayer(source); + groupUpdateMsg.setMessageType(3); + group.sendUpdate(groupUpdateMsg); + + } + + //This updates health/stamina/mana and loc of all players in group + + public static void RefreshWholeGroupList(PlayerCharacter source, ClientConnection origin, Group gexp) { + + if (source == null || origin == null) + return; + + Group group = GroupManager.groupsByAC.get(source); + + if (group == null) + return; + + if (gexp.getObjectUUID() != group.getObjectUUID()) + return; + + Set groupMembers = group.getMembers(); + + if (groupMembers.size() < 2) + return; + + // Send all group members health/mana/stamina/loc. + + for (PlayerCharacter groupMember : groupMembers) { + + if (groupMember == null) + continue; + + GroupUpdateMsg gum = new GroupUpdateMsg(5, 1, groupMembers, group); + gum.setPlayerUUID(groupMember.getObjectUUID()); + + Dispatch dispatch = Dispatch.borrow(groupMember, gum); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + } + + public static void RefreshMyGroupList(PlayerCharacter source, ClientConnection origin) { + + if (source == null || origin == null) + return; + + Group group = GroupManager.groupsByAC.get(source); + + if (group == null) + return; + + Set members = group.getMembers(); + + + // Send all group members to player added + for (PlayerCharacter groupMember : members) { + + if (groupMember == null) + continue; + + GroupUpdateMsg gum = new GroupUpdateMsg(); + gum.setGroup(group); + gum.setMessageType(1); + gum.setPlayer(groupMember); + Dispatch dispatch = Dispatch.borrow(groupMember, gum); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + } + } + + public static void RefreshMyGroupListSinglePlayer(PlayerCharacter source, ClientConnection origin, PlayerCharacter playerToRefresh) { + + // send msg type 1 to the source player on this connection to update the group + // list stats for the player that has just been loaded + + if (source == null || origin == null || playerToRefresh == null) + return; + + Group group = GroupManager.groupsByAC.get(source); + + if (group == null) + return; + + // only send if the 2 players are in the same group + if (group != GroupManager.groupsByAC.get(playerToRefresh)) + return; + + GroupUpdateMsg gum = new GroupUpdateMsg(); + gum.setGroup(group); + gum.setMessageType(1); + gum.setPlayer(playerToRefresh); + + Dispatch dispatch = Dispatch.borrow(source, gum); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } + + public static void RefreshOthersGroupList(PlayerCharacter source) { + + // refresh my stats on everyone elses group list + + if (source == null) + return; + + Group group = GroupManager.groupsByAC.get(source); + + if (group == null) + return; + + //construct message + GroupUpdateMsg gim = new GroupUpdateMsg(); + gim.setGroup(group); + gim.setMessageType(1); + gim.setPlayer(source); + group.sendUpdate(gim); + + } + + public static int incrGroupCount() { + GroupManager.groupCount++; + return GroupManager.groupCount; + } + + public static boolean deleteGroup(Group g) { + + // remove all players from the mapping + Set members = g.getMembers(); + + for (PlayerCharacter pc : members) { + if (pc != null) { + GroupManager.removeFromGroups(pc); + } + } + // remove the group ID from the list + GroupManager.groupsByID.remove(g.getObjectUUID()); + g.clearMembers(); + + g.removeUpdateGroupJob(); + + return true; + } + + public static Group addNewGroup(Group group) { + + PlayerCharacter pc = group.getGroupLead(); + + GroupManager.addGroup(group); + + if (pc != null) { + GroupManager.addPlayerGroupMapping(pc, group); + return group; + } + return null; + + } + + private static Group addGroup(Group group) { + + if (GroupManager.groupsByID.containsKey(group.getObjectUUID())) { + return null; + } + + GroupManager.groupsByID.put(group.getObjectUUID(), group); + return group; + } + + public static Group getGroup(int groupID) { + return GroupManager.groupsByID.get(groupID); + } + + public static Group getGroup(PlayerCharacter pc) { + + return GroupManager.groupsByAC.get(pc); + } + + public static void addPlayerGroupMapping(PlayerCharacter pc, Group grp) { + GroupManager.groupsByAC.put(pc, grp); + } + + public static boolean goldSplit(PlayerCharacter pc, Item item, ClientConnection origin, AbstractWorldObject tar) { + if (item == null || pc == null || tar == null || item.getItemBase() == null) { + Logger.error("null something"); + return false; + } + + if (item.getItemBase().getUUID() != 7) //only split goldItem + return false; + + Group group = getGroup(pc); + + if (group == null || !group.getSplitGold()) //make sure player is grouped and split is on + return false; + + + ArrayList playersSplit = new ArrayList<>(); + + //get group members + + for (PlayerCharacter groupMember : group.getMembers()) { + if (pc.getLoc().distanceSquared2D(groupMember.getLoc()) > MBServerStatics.CHARACTER_LOAD_RANGE * MBServerStatics.CHARACTER_LOAD_RANGE) + continue; + + if (!groupMember.isAlive()) + continue; + + playersSplit.add(groupMember); + } + + + //make sure more then one group member in loot range + int size = playersSplit.size(); + + if (size < 2) + return false; + + int total = item.getNumOfItems(); + int amount = total / size; + int dif = total - (size * amount); + + if (AbstractWorldObject.IsAbstractCharacter(tar)) { + } else if (tar.getObjectType().equals(Enum.GameObjectType.Corpse)) { + Corpse corpse = (Corpse) tar; + corpse.getInventory().remove(item); + } else { + Logger.error("target not corpse or character"); + return false; + } + + if (item.getObjectType() == Enum.GameObjectType.MobLoot) { + if (tar.getObjectType() == Enum.GameObjectType.Mob) { + ((Mob) tar).getCharItemManager().delete(item); + } else + item.setNumOfItems(0); + } else + item.setNumOfItems(0); + for (PlayerCharacter splitPlayer : playersSplit) { + + + int amt = (group.isGroupLead(splitPlayer)) ? (amount + dif) : amount; + if (amt > 0) + splitPlayer.getCharItemManager().addGoldToInventory(amt, false); + } + + for (PlayerCharacter splitPlayer : playersSplit) { + + + UpdateGoldMsg ugm = new UpdateGoldMsg(splitPlayer); + ugm.configure(); + + Dispatch dispatch = Dispatch.borrow(splitPlayer, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + UpdateGoldMsg updateTargetGold = new UpdateGoldMsg(tar); + updateTargetGold.configure(); + DispatchMessage.dispatchMsgToInterestArea(tar, updateTargetGold, Enum.DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + + // //TODO send group split message + String text = "Group Split: " + amount; + ChatManager.chatGroupInfo(pc, text); + + return true; + } +} diff --git a/src/engine/gameManager/GuildManager.java b/src/engine/gameManager/GuildManager.java new file mode 100644 index 00000000..e6b285ae --- /dev/null +++ b/src/engine/gameManager/GuildManager.java @@ -0,0 +1,204 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.GuildHistoryType; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.guild.AcceptInviteToGuildMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.objects.*; +import org.joda.time.DateTime; + +public enum GuildManager { + + GUILDMANAGER; + + //Guild Error Message + public static final int FAILURE_TO_SWEAR_GUILD = 45; //45: Failure to swear guild + public static final int MUST_LEAVE_GUILD = 75;//75: You must leave your current guild before you can repledge + public static final int NO_CHARTER_FOUND = 148; //148: Unable to find a matching petition to complete guild creation + public static final int PROFANE_NAME = 149; //149: Guild name fails profanity check + public static final int PROFANE_MOTTO = 150; //150: Guild motto fails profanity check + public static final int UNIQUE_NAME = 151;//151: Guild name is not unique + public static final int UNIQUE_CREST = 152;//152: Guild crest is not unique + public static final int CREST_RESERVED = 153; //153: Guild crest is reserved + public static final int CREST_COLOR_ERROR = 154; //154: All three crest colors cannot be the same + + public static boolean joinGuild(PlayerCharacter pc, Guild guild, GuildHistoryType historyType) { + return joinGuild(pc, guild, 0, historyType); + } + + //Used when repledging + public static boolean joinGuild(PlayerCharacter pc, Guild guild, int cityID, GuildHistoryType historyType) { + return joinGuild(pc, guild, cityID, true, historyType); + } + + public static boolean joinGuild(PlayerCharacter playerCharacter, Guild guild, int cityID, boolean fromTeleportScreen, GuildHistoryType historyType) { + + // Member variable delcaration + + ClientConnection origin; + AcceptInviteToGuildMsg msg; + Dispatch dispatch; + + if (playerCharacter == null || guild == null) + return false; + + // Member variable assignment + + origin = SessionManager.getClientConnection(playerCharacter); + + if (origin == null) + return false; + + if (playerCharacter.getGuild().isEmptyGuild() == false && GuildStatusController.isGuildLeader(playerCharacter.getGuildStatus())) + return false; + + if (playerCharacter.getGuild() != null && playerCharacter.getGuild().isGuildLeader(playerCharacter.getObjectUUID())) + return false; + + if (playerCharacter.getGuild() != null && !playerCharacter.getGuild().isEmptyGuild()) { + if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(playerCharacter.getGuildUUID(), playerCharacter, DateTime.now(), GuildHistoryType.LEAVE)) { + GuildHistory guildHistory = new GuildHistory(playerCharacter.getGuildUUID(), playerCharacter.getGuild().getName(), DateTime.now(), GuildHistoryType.LEAVE); + playerCharacter.getGuildHistory().add(guildHistory); + } + } + + playerCharacter.setInnerCouncil(false); + playerCharacter.setGuildLeader(false); + playerCharacter.setGuild(guild); + + // Cleanup guild stuff + playerCharacter.resetGuildStatuses(); + + // send success message to client + if (fromTeleportScreen && guild.isNPCGuild()) + playerCharacter.setFullMember(true); + + msg = new AcceptInviteToGuildMsg(guild.getObjectUUID(), 1, 0); + + if (fromTeleportScreen) { + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), playerCharacter, DateTime.now(), historyType)) { + GuildHistory guildHistory = new GuildHistory(guild.getObjectUUID(), guild.getName(), DateTime.now(), historyType); + playerCharacter.getGuildHistory().add(guildHistory); + } + + DispatchMessage.sendToAllInRange(playerCharacter, new GuildInfoMsg(playerCharacter, guild, 2)); + + // Send guild join message + ChatManager.chatGuildInfo(playerCharacter, + playerCharacter.getFirstName() + " has joined the guild"); + + playerCharacter.incVer(); + + return true; + // TODO update player to world + } + + public static void enterWorldMOTD(PlayerCharacter pc) { + + Guild guild; + Guild nation; + + if (pc == null) { + return; + } + + guild = pc.getGuild(); + + if (guild == null || guild.getObjectUUID() == 0) // Don't send to errant + return; + + // Send Guild MOTD + String motd = guild.getMOTD(); + if (motd.length() > 0) { + ChatManager.chatGuildMOTD(pc, motd); + } + + // Send Nation MOTD + nation = guild.getNation(); + + if (nation != null) { + if (nation.getObjectUUID() != 0) { // Don't send to errant nation + motd = nation.getMOTD(); + if (motd.length() > 0) { + ChatManager.chatNationMOTD(pc, motd); + } + } + } + + // Send IC MOTD if player is IC + if (GuildStatusController.isInnerCouncil(pc.getGuildStatus())) { + motd = guild.getICMOTD(); + if (motd.length() > 0) { + ChatManager.chatICMOTD(pc, motd); + } + } + } + + //Updates the bind point for everyone in guild + + public static void updateAllGuildBinds(Guild guild, City city) { + + if (guild == null) + return; + + int cityID = (city != null) ? city.getObjectUUID() : 0; + + + //update binds ingame + + + for (PlayerCharacter playerCharacter : Guild.GuildRoster(guild)) { + boolean updateBindBuilding = false; + + Building oldBoundBuilding = BuildingManager.getBuildingFromCache(playerCharacter.getBindBuildingID()); + + if (oldBoundBuilding == null || oldBoundBuilding.getBlueprint() == null || oldBoundBuilding.getBlueprint().getBuildingGroup().equals(BuildingGroup.TOL)) + updateBindBuilding = true; + + + if (updateBindBuilding) { + Building bindBuilding = null; + if (city != null) + if (city.getTOL() != null) + bindBuilding = city.getTOL(); + + if (bindBuilding == null) + bindBuilding = PlayerCharacter.getBindBuildingForGuild(playerCharacter); + + playerCharacter.setBindBuildingID(bindBuilding != null ? bindBuilding.getObjectUUID() : 0); + } + + + } + } + + //This updates tags for all online players in a guild. + public static void updateAllGuildTags(Guild guild) { + + if (guild == null) + return; + + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) { + + if (player.getGuild().equals(guild)) + DispatchMessage.sendToAllInRange(player, new GuildInfoMsg(player, guild, 2)); + + } + } + +} diff --git a/src/engine/gameManager/LootManager.java b/src/engine/gameManager/LootManager.java new file mode 100644 index 00000000..a569324d --- /dev/null +++ b/src/engine/gameManager/LootManager.java @@ -0,0 +1,457 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum; +import engine.loot.*; +import engine.net.DispatchMessage; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.chat.ChatSystemMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Class contains static methods for data from Magicbane's loot tables + */ +public enum LootManager { + + LOOTMANAGER; + + // Newer tables + + public static HashMap> _genTables = new HashMap<>(); + public static HashMap> _itemTables = new HashMap<>(); + public static HashMap> _modTables = new HashMap<>(); + public static HashMap> _modTypeTables = new HashMap<>(); + + // Drop Rates + + public static float NORMAL_DROP_RATE; + public static float NORMAL_EXP_RATE; + public static float NORMAL_GOLD_RATE; + public static float HOTZONE_DROP_RATE; + public static float HOTZONE_EXP_RATE; + public static float HOTZONE_GOLD_RATE; + public static HashMap> _bootySetMap = new HashMap<>(); + + // Bootstrap routine to initialize the Loot Manager + + public static void init() { + + // Load loot tables from database. + + _genTables = DbManager.LootQueries.LOAD_GEN_ITEM_TABLES(); + _itemTables = DbManager.LootQueries.LOAD_ITEM_TABLES(); + _modTables = DbManager.LootQueries.LOAD_MOD_TABLES(); + _modTypeTables = DbManager.LootQueries.LOAD_MOD_TYPE_TABLES(); + + // Cache drop rate values from Config manager. + + NORMAL_DROP_RATE = Float.parseFloat(ConfigManager.MB_NORMAL_DROP_RATE.getValue()); + NORMAL_EXP_RATE = Float.parseFloat(ConfigManager.MB_NORMAL_EXP_RATE.getValue()); + NORMAL_GOLD_RATE = Float.parseFloat(ConfigManager.MB_NORMAL_GOLD_RATE.getValue()); + HOTZONE_DROP_RATE = Float.parseFloat(ConfigManager.MB_HOTZONE_DROP_RATE.getValue()); + HOTZONE_EXP_RATE = Float.parseFloat(ConfigManager.MB_HOTZONE_EXP_RATE.getValue()); + HOTZONE_GOLD_RATE = Float.parseFloat(ConfigManager.MB_HOTZONE_GOLD_RATE.getValue()); + + } + + public static void GenerateMobLoot(Mob mob) { + + //determine if mob is in hotzone + boolean inHotzone = ZoneManager.inHotZone(mob.getLoc()); + + //iterate the booty sets + + if (mob.getMobBase().bootySet != 0 && _bootySetMap.containsKey(mob.getMobBase().bootySet) == true) + RunBootySet(_bootySetMap.get(mob.getMobBase().bootySet), mob, inHotzone); + + if (mob.bootySet != 0 && _bootySetMap.containsKey(mob.bootySet) == true) + RunBootySet(_bootySetMap.get(mob.bootySet), mob, inHotzone); + + //lastly, check mobs inventory for godly or disc runes to send a server announcement + for (Item it : mob.getInventory()) { + + ItemBase ib = it.getItemBase(); + if(ib == null) + break; + if (ib.isDiscRune() || ib.getName().toLowerCase().contains("of the gods")) { + ChatSystemMsg chatMsg = new ChatSystemMsg(null, mob.getName() + " in " + mob.getParentZone().getName() + " has found the " + ib.getName() + ". Are you tough enough to take it?"); + chatMsg.setMessageType(10); + chatMsg.setChannel(Enum.ChatChannelType.SYSTEM.getChannelID()); + DispatchMessage.dispatchMsgToAll(chatMsg); + } + } + + } + + private static void RunBootySet(ArrayList entries, Mob mob, boolean inHotzone) { + + boolean hotzoneWasRan = false; + float dropRate = 1.0f; + + // Iterate all entries in this bootySet and process accordingly + + for (BootySetEntry bse : entries) { + switch (bse.bootyType) { + case "GOLD": + GenerateGoldDrop(mob, bse, inHotzone); + break; + case "LOOT": + + if (mob.getSafeZone() == false) + dropRate = LootManager.NORMAL_DROP_RATE; + + if (inHotzone == true) + dropRate = LootManager.HOTZONE_DROP_RATE; + + if (ThreadLocalRandom.current().nextInt(1, 100 + 1) < (bse.dropChance * dropRate)) + GenerateLootDrop(mob, bse.genTable, false); //generate normal loot drop + + // Generate hotzone loot if in hotzone + // Only one bite at the hotzone apple per bootyset. + + if (inHotzone == true && hotzoneWasRan == false) + if (_genTables.containsKey(bse.genTable + 1) && ThreadLocalRandom.current().nextInt(1, 100 + 1) < (bse.dropChance * dropRate)) { + GenerateLootDrop(mob, bse.genTable + 1, true); //generate loot drop from hotzone table + hotzoneWasRan = true; + } + + break; + case "ITEM": + GenerateInventoryDrop(mob, bse); + break; + } + } + } + + public static MobLoot getGenTableItem(int genTableID, AbstractCharacter mob, Boolean inHotzone) { + + if (mob == null || _genTables.containsKey(genTableID) == false) + return null; + + MobLoot outItem; + + int genRoll = ThreadLocalRandom.current().nextInt(1,100 + 1); + + GenTableEntry selectedRow = GenTableEntry.rollTable(genTableID, genRoll, 1.0f); + + if (selectedRow == null) + return null; + + int itemTableId = selectedRow.itemTableID; + + if (_itemTables.containsKey(itemTableId) == false) + return null; + + //gets the 1-320 roll for this mob + int itemTableRoll = 0; + int objectType = mob.getObjectType().ordinal(); + if(mob.getObjectType().ordinal() == 52) { //52 = player character + itemTableRoll = ThreadLocalRandom.current().nextInt(1,320 + 1); + } else{ + itemTableRoll = TableRoll(mob.level, inHotzone); + } + ItemTableEntry tableRow = ItemTableEntry.rollTable(itemTableId, itemTableRoll); + if (tableRow == null) + return null; + + int itemUUID = tableRow.cacheID; + + if (itemUUID == 0) + return null; + + if (ItemBase.getItemBase(itemUUID).getType().ordinal() == Enum.ItemType.RESOURCE.ordinal()) { + int amount = ThreadLocalRandom.current().nextInt(tableRow.minSpawn, tableRow.maxSpawn + 1); + return new MobLoot(mob, ItemBase.getItemBase(itemUUID), amount, false); + } + + outItem = new MobLoot(mob, ItemBase.getItemBase(itemUUID), false); + Enum.ItemType outType = outItem.getItemBase().getType(); + + + if(selectedRow.pModTable != 0){ + try { + outItem = GeneratePrefix(mob, outItem, genTableID, genRoll, inHotzone); + outItem.setIsID(false); + } catch (Exception e) { + Logger.error("Failed to GeneratePrefix for item: " + outItem.getName()); + } + } + if(selectedRow.sModTable != 0){ + try { + outItem = GenerateSuffix(mob, outItem, genTableID, genRoll, inHotzone); + outItem.setIsID(false); + } catch (Exception e) { + Logger.error("Failed to GenerateSuffix for item: " + outItem.getName()); + } + } + return outItem; + } + + private static MobLoot GeneratePrefix(AbstractCharacter mob, MobLoot inItem, int genTableID, int genRoll, Boolean inHotzone) { + + GenTableEntry selectedRow = GenTableEntry.rollTable(genTableID, genRoll, 1.0f); + + if (selectedRow == null) + return inItem; + + int prefixroll = ThreadLocalRandom.current().nextInt(1, 100 + 1); + + ModTypeTableEntry prefixTable = ModTypeTableEntry.rollTable(selectedRow.pModTable, prefixroll); + + if (prefixTable == null) + return inItem; + int prefixTableRoll = 0; + if(mob.getObjectType().ordinal() == 52) { + prefixTableRoll = ThreadLocalRandom.current().nextInt(1,320 + 1); + } else{ + prefixTableRoll = TableRoll(mob.level, inHotzone); + } + ModTableEntry prefixMod = ModTableEntry.rollTable(prefixTable.modTableID, prefixTableRoll); + + if (prefixMod == null) + return inItem; + + if (prefixMod.action.length() > 0) { + inItem.setPrefix(prefixMod.action); + inItem.addPermanentEnchantment(prefixMod.action, 0, prefixMod.level, true); + } + + return inItem; + } + + private static MobLoot GenerateSuffix(AbstractCharacter mob, MobLoot inItem, int genTableID, int genRoll, Boolean inHotzone) { + + GenTableEntry selectedRow = GenTableEntry.rollTable(genTableID, genRoll, 1.0f); + + if (selectedRow == null) + return inItem; + + int suffixRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); + + ModTypeTableEntry suffixTable = ModTypeTableEntry.rollTable(selectedRow.sModTable, suffixRoll); + + if (suffixTable == null) + return inItem; + int suffixTableRoll = 0; + if(mob.getObjectType().ordinal() == 52) { + suffixTableRoll = ThreadLocalRandom.current().nextInt(1,320 + 1); + } else{ + suffixTableRoll = TableRoll(mob.level, inHotzone); + } + ModTableEntry suffixMod = ModTableEntry.rollTable(suffixTable.modTableID, suffixTableRoll); + + if (suffixMod == null) + return inItem; + + if (suffixMod.action.length() > 0) { + inItem.setSuffix(suffixMod.action); + inItem.addPermanentEnchantment(suffixMod.action, 0, suffixMod.level, false); + } + + return inItem; + } + + public static int TableRoll(int mobLevel, Boolean inHotzone) { + + if (mobLevel > 65) + mobLevel = 65; + + int max = (int) (4.882 * mobLevel + 127.0); + + if (max > 319) + max = 319; + + int min = (int) (4.469 * mobLevel - 3.469); + + if (min < 70) + min = 70; + + if (inHotzone) + min += mobLevel; + + int roll = ThreadLocalRandom.current().nextInt(min, max + 1); + + return roll; + } + + public static void GenerateGoldDrop(Mob mob, BootySetEntry bse, Boolean inHotzone) { + + int chanceRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); + + //early exit, failed to hit minimum chance roll + + if (chanceRoll > bse.dropChance) + return; + + //determine and add gold to mob inventory + + int high = bse.highGold; + int low = bse.lowGold; + int gold = ThreadLocalRandom.current().nextInt(low, high + 1); + + if (inHotzone == true) + gold = (int) (gold * HOTZONE_GOLD_RATE); + else + gold = (int) (gold * NORMAL_GOLD_RATE); + + if (gold > 0) { + MobLoot goldAmount = new MobLoot(mob, gold); + mob.getCharItemManager().addItemToInventory(goldAmount); + } + + } + + public static void GenerateLootDrop(Mob mob, int tableID, Boolean inHotzone) { + + try { + + MobLoot toAdd = getGenTableItem(tableID, mob, inHotzone); + + if (toAdd != null) + mob.getCharItemManager().addItemToInventory(toAdd); + + } catch (Exception e) { + //TODO chase down loot generation error, affects roughly 2% of drops + int i = 0; + } + } + + public static void GenerateEquipmentDrop(Mob mob) { + + //do equipment here + int dropCount = 0; + if (mob.getEquip() != null) + for (MobEquipment me : mob.getEquip().values()) { + + if (me.getDropChance() == 0) + continue; + + float equipmentRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); + float dropChance = me.getDropChance() * 100; + + if (equipmentRoll > dropChance) + continue; + + MobLoot ml = new MobLoot(mob, me.getItemBase(), false); + + if (ml != null && dropCount < 1) { + ml.setIsID(true); + ml.setDurabilityCurrent((short) (ml.getDurabilityCurrent() - ThreadLocalRandom.current().nextInt(5) + 1)); + mob.getCharItemManager().addItemToInventory(ml); + dropCount = 1; + //break; // Exit on first successful roll. + } + } + } + + public static void GenerateInventoryDrop(Mob mob, BootySetEntry bse) { + + int chanceRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); + + //early exit, failed to hit minimum chance roll + + if (chanceRoll > bse.dropChance) + return; + + MobLoot lootItem = new MobLoot(mob, ItemBase.getItemBase(bse.itemBase), true); + + if (lootItem != null) + mob.getCharItemManager().addItemToInventory(lootItem); + } + + public static void peddleFate(PlayerCharacter playerCharacter, Item gift) { + + //get table ID for the itembase ID + + int tableID = 0; + + if (_bootySetMap.get(gift.getItemBaseID()) != null) + tableID = _bootySetMap.get(gift.getItemBaseID()).get(ThreadLocalRandom.current().nextInt(_bootySetMap.get(gift.getItemBaseID()).size())).genTable; + + if (tableID == 0) + return; + + //get the character item manager + + CharacterItemManager itemMan = playerCharacter.getCharItemManager(); + + if (itemMan == null) + return; + + //check if player owns the gift he is trying to open + + if (itemMan.doesCharOwnThisItem(gift.getObjectUUID()) == false) + return; + + //roll 1-100 for the gen table selection + + int genRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); + GenTableEntry selectedRow = GenTableEntry.rollTable(tableID, genRoll, LootManager.NORMAL_DROP_RATE); + + if(selectedRow == null) + return; + + //roll 220-320 for the item table selection + + int itemRoll = ThreadLocalRandom.current().nextInt(220, 320 + 1); + ItemTableEntry selectedItem = ItemTableEntry.rollTable(selectedRow.itemTableID, itemRoll); + + if (selectedItem == null) + return; + + //create the item from the table, quantity is always 1 + + MobLoot winnings = new MobLoot(playerCharacter, ItemBase.getItemBase(selectedItem.cacheID), 1, false); + + if (winnings == null) + return; + + //early exit if the inventory of the player will not old the item + + if (itemMan.hasRoomInventory(winnings.getItemBase().getWeight()) == false) { + ErrorPopupMsg.sendErrorPopup(playerCharacter, 21); + return; + } + + //determine if the winning item needs a prefix + + if(selectedRow.pModTable != 0){ + int prefixRoll = ThreadLocalRandom.current().nextInt(220,320 + 1); + ModTableEntry prefix = ModTableEntry.rollTable(selectedRow.pModTable, prefixRoll); + if(prefix != null) + winnings.addPermanentEnchantment(prefix.action, 0, prefix.level, true); + } + + //determine if the winning item needs a suffix + + if(selectedRow.sModTable != 0){ + int suffixRoll = ThreadLocalRandom.current().nextInt(220,320 + 1); + ModTableEntry suffix = ModTableEntry.rollTable(selectedRow.sModTable, suffixRoll); + if (suffix != null) + winnings.addPermanentEnchantment(suffix.action, 0, suffix.level, true); + } + winnings.setIsID(true); + + //remove gift from inventory + + itemMan.consume(gift); + + //add winnings to player inventory + + Item playerWinnings = winnings.promoteToItem(playerCharacter); + itemMan.addItemToInventory(playerWinnings); + itemMan.updateInventory(); + } +} diff --git a/src/engine/gameManager/MaintenanceManager.java b/src/engine/gameManager/MaintenanceManager.java new file mode 100644 index 00000000..f92d531e --- /dev/null +++ b/src/engine/gameManager/MaintenanceManager.java @@ -0,0 +1,324 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +// Defines static methods which comprise the magicbane +// building maintenance system. + +import engine.Enum; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.time.LocalDateTime; +import java.util.ArrayList; + +public enum MaintenanceManager { + + MAINTENANCEMANAGER; + + public static void setMaintDateTime(Building building, LocalDateTime maintDate) { + + building.maintDateTime = maintDate; + DbManager.BuildingQueries.updateMaintDate(building); + + } + + public static void processBuildingMaintenance() { + + ArrayList buildingList; + ArrayList maintList; + ArrayList derankList = new ArrayList<>(); + + Logger.info("Starting Maintenance on Player Buildings"); + + // Build list of buildings to apply maintenance on. + + buildingList = new ArrayList(DbManager.getList(Enum.GameObjectType.Building)); + maintList = buildMaintList(buildingList); + + // Deduct upkeep and build list of buildings + // which did not have funds available + + for (Building building : maintList) { + + if (chargeUpkeep(building) == false) + derankList.add(building); + } + // Reset maintenance dates for these buildings + + for (Building building : maintList) { + setMaintDateTime(building, LocalDateTime.now().plusDays(7)); + + } + // Derak or destroy buildings that did not + // have funds available. + + for (Building building : derankList) + building.destroyOrDerank(null); + + Logger.info("Structures: " + buildingList.size() + " Maint: " + maintList.size() + " Derank: " + derankList.size()); + } + + // Iterate over all buildings in game and apply exclusion rules + // returning a list of building for which maintenance is due. + + private static ArrayList buildMaintList(ArrayList buildingList) { + + ArrayList maintList = new ArrayList<>(); + + for (AbstractGameObject gameObject : buildingList) { + + Building building = (Building) gameObject; + + // No maintenance on NPC owned buildings (Cache loaded) + + if (building.getProtectionState() == Enum.ProtectionState.NPC) + continue; + + // No maintenance on constructing meshes + + if (building.getRank() < 1) + continue; + + // No Maintenance on furniture + + if (building.parentBuildingID != 0) + continue; + + // No Blueprint? + + if (building.getBlueprint() == null) { + Logger.error("Blueprint missing for uuid: " + building.getObjectUUID()); + continue; + } + + // No maintenance on banestones omfg + + if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.BANESTONE)) + continue; + + // no maintenance on Mines omfg + + if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.MINE)) + continue; + + // Null Maintenance date? + + if (building.maintDateTime == null) { + Logger.error("Null maint date for building UUID: " + building.getObjectUUID()); + continue; + } + + // Maintenance date is in the future + + if (building.maintDateTime.isAfter(LocalDateTime.now())) + continue; + + + //no maintenance if day of week doesnt match + if (LocalDateTime.now().getDayOfWeek().ordinal() != building.maintDateTime.getDayOfWeek().ordinal()) { + continue; + } + // Add building to maintenance queue + + maintList.add(building); + } + + return maintList; + } + + // Method removes the appropriate amount of gold/resources from + // a building according to it's maintenance schedule. True/False + // is returned indicating if the building had enough funds to cover. + + public static boolean chargeUpkeep(Building building) { + + City city = null; + Warehouse warehouse = null; + int maintCost = 0; + int overDraft = 0; + boolean hasFunds = false; + boolean hasResources = false; + int resourceValue = 0; + + city = building.getCity(); + + if (city != null) + warehouse = city.getWarehouse(); + + // Cache maintenance cost value + + maintCost = building.getMaintCost(); + + // Something went wrong. Missing buildinggroup from switch? + + if (maintCost == 0) { + Logger.error("chargeUpkeep", "Error retrieving rankcost for " + building.getName() + " uuid:" + building.getObjectUUID() + "buildinggroup:" + building.getBlueprint().getBuildingGroup().name()); + // check if there is enough gold on the building + return true; + } + + if (building.getStrongboxValue() >= maintCost) + hasFunds = true; + + // If we cannot cover with just the strongbox + // see if there is a warehouse that will cover + // the overdraft for us. + + + if (hasFunds == false && (building.assetIsProtected() || building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.WAREHOUSE)) { + overDraft = maintCost - building.getStrongboxValue(); + } + + if ((overDraft > 0)) + if ((building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.SHRINE) == false) && + (warehouse != null) && building.assetIsProtected() == true && + (warehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) >= overDraft) { + hasFunds = true; + } + + // If this is an R8 tree, validate that we can + // cover the resources required + + if (building.getRank() == 8) { + + hasResources = true; + + if (warehouse == null) + hasResources = false; + else { + + resourceValue = warehouse.getResources().get(Warehouse.stoneIB); + + if (resourceValue < 1500) + hasResources = false; + + resourceValue = warehouse.getResources().get(Warehouse.lumberIB); + + if (resourceValue < 1500) + hasResources = false; + + resourceValue = warehouse.getResources().get(Warehouse.galvorIB); + + if (resourceValue < 5) + hasResources = false; + + resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB); + + if (resourceValue < 5) + hasResources = false; + + } + } + // Validation completed but has failed. We can derank + // the target building and early exit + + if ((hasFunds == false) || + ((building.getRank() == 8) && !hasResources)) { + + // Add cash back to strongbox for lost rank if the building isn't being destroyed + // and it's not an R8 deranking + + if ((building.getRank() > 1) && (building.getRank() < 8)) { + building.setStrongboxValue(building.getStrongboxValue() + building.getBlueprint().getRankCost(Math.min(building.getRank(), 7))); + } + + return false; // Early exit for having failed to meet maintenance + } + + // Remove cash and resources + + // withdraw what we can from the building + + building.setStrongboxValue(building.getStrongboxValue() - (maintCost - overDraft)); + + // withdraw overdraft from the whorehouse + + if (overDraft > 0) { + + resourceValue = warehouse.getResources().get(Warehouse.goldIB); + + if (DbManager.WarehouseQueries.updateGold(warehouse, resourceValue - overDraft) == true) { + warehouse.getResources().put(Warehouse.goldIB, resourceValue - overDraft); + warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GOLD, overDraft); + } else { + Logger.error("gold update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return true; + } + } + + // Early exit as we're done if we're not an R8 tree + + if (building.getRank() < 8) + return true; + + // Now for the resources if it's an R8 tree + + // Withdraw Stone + + resourceValue = warehouse.getResources().get(Warehouse.stoneIB); + + if (DbManager.WarehouseQueries.updateStone(warehouse, resourceValue - 1500) == true) { + warehouse.getResources().put(Warehouse.stoneIB, resourceValue - 1500); + warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.STONE, 1500); + } else { + Logger.error("stone update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return true; + } + + // Withdraw Lumber + + resourceValue = warehouse.getResources().get(Warehouse.lumberIB); + + if (DbManager.WarehouseQueries.updateLumber(warehouse, resourceValue - 1500) == true) { + warehouse.getResources().put(Warehouse.lumberIB, resourceValue - 1500); + warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.LUMBER, 1500); + } else { + Logger.error("lumber update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return true; + } + + // Withdraw Galvor + + resourceValue = warehouse.getResources().get(Warehouse.galvorIB); + + if (DbManager.WarehouseQueries.updateGalvor(warehouse, resourceValue - 5) == true) { + warehouse.getResources().put(Warehouse.galvorIB, resourceValue - 5); + warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GALVOR, 5); + } else { + Logger.error("galvor update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return true; + } + + resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB); + + if (DbManager.WarehouseQueries.updateWormwood(warehouse, resourceValue - 5) == true) { + warehouse.getResources().put(Warehouse.wormwoodIB, resourceValue - 5); + warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.WORMWOOD, 5); + } else { + Logger.error("wyrmwood update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + } + + return true; + } + + public static void dailyMaintenance() { + + Logger.info("Maintenance has started"); + + // Run maintenance on player buildings + + if (ConfigManager.MB_WORLD_MAINTENANCE.getValue().equalsIgnoreCase("true")) + processBuildingMaintenance(); + else + Logger.info("Maintenance Costings: DISABLED"); + + Logger.info("Maintenance has completed!"); + } +} diff --git a/src/engine/gameManager/MovementManager.java b/src/engine/gameManager/MovementManager.java new file mode 100644 index 00000000..f38b502e --- /dev/null +++ b/src/engine/gameManager/MovementManager.java @@ -0,0 +1,500 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum.DispatchChannel; +import engine.Enum.GameObjectType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.InterestManagement.InterestManager; +import engine.exception.MsgSendException; +import engine.math.Bounds; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.MoveToPointMsg; +import engine.net.client.msg.TeleportToPointMsg; +import engine.net.client.msg.UpdateStateMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.Set; + +import static engine.math.FastMath.sqr; + +public enum MovementManager { + + MOVEMENTMANAGER; + + private static final String changeAltitudeTimerJobName = "ChangeHeight"; + private static final String flightTimerJobName = "Flight"; + + public static void sendOOS(PlayerCharacter pc) { + pc.setWalkMode(true); + MovementManager.sendRWSSMsg(pc); + } + + public static void sendRWSSMsg(AbstractCharacter ac) { + + if (!ac.isAlive()) + return; + UpdateStateMsg rssm = new UpdateStateMsg(); + rssm.setPlayer(ac); + if (ac.getObjectType() == GameObjectType.PlayerCharacter) + DispatchMessage.dispatchMsgToInterestArea(ac, rssm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + DispatchMessage.sendToAllInRange(ac, rssm); + } + + /* + * Sets the first combat target for the AbstractCharacter. Used to clear the + * combat + * target upon each move, unless something has set the firstHitCombatTarget + * Also used to determine the size of a monster's hitbox + */ + public static void movement(MoveToPointMsg msg, AbstractCharacter toMove) throws MsgSendException { + + // check for stun/root + if (!toMove.isAlive()) + return; + + if (toMove.getObjectType().equals(GameObjectType.PlayerCharacter)) { + if (((PlayerCharacter) toMove).isCasting()) + ((PlayerCharacter) toMove).update(); + } + + + toMove.setIsCasting(false); + toMove.setItemCasting(false); + + if (toMove.getBonuses().getBool(ModType.Stunned, SourceType.None) || toMove.getBonuses().getBool(ModType.CannotMove, SourceType.None)) { + return; + } + + if (msg.getEndLat() > MBServerStatics.MAX_WORLD_WIDTH) + msg.setEndLat((float) MBServerStatics.MAX_WORLD_WIDTH); + + if (msg.getEndLon() < MBServerStatics.MAX_WORLD_HEIGHT) { + msg.setEndLon((float) MBServerStatics.MAX_WORLD_HEIGHT); + } + +// if (msg.getEndLat() < 0) +// msg.setEndLat(0); +// +// if (msg.getEndLon() > 0) +// msg.setEndLon(0); + + + if (!toMove.isMoving()) + toMove.resetLastSetLocUpdate(); + else + toMove.update(); + + // Update movement for the player + + + // else if (toMove.getObjectType() == GameObjectType.Mob) + // ((Mob)toMove).updateLocation(); + // get start and end locations for the move + Vector3fImmutable startLocation = new Vector3fImmutable(msg.getStartLat(), msg.getStartAlt(), msg.getStartLon()); + Vector3fImmutable endLocation = new Vector3fImmutable(msg.getEndLat(), msg.getEndAlt(), msg.getEndLon()); + + // if (toMove.getObjectType() == GameObjectType.PlayerCharacter) + // if (msg.getEndAlt() == 0 && msg.getTargetID() == 0){ + // MovementManager.sendRWSSMsg(toMove); + // } + + //If in Building, let's see if we need to Fix + + // if inside a building, convert both locations from the building local reference frame to the world reference frame + + if (msg.getTargetID() > 0) { + Building building = BuildingManager.getBuildingFromCache(msg.getTargetID()); + if (building != null) { + + Vector3fImmutable convertLocEnd = new Vector3fImmutable(ZoneManager.convertLocalToWorld(building, endLocation)); + // if (!Bounds.collide(convertLocEnd, b) || !b.loadObjectsInside()) { + // toMove.setInBuilding(-1); + // toMove.setInFloorID(-1); + // toMove.setInBuildingID(0); + // } + // else { + toMove.setInBuilding(msg.getInBuilding()); + toMove.setInFloorID(msg.getUnknown01()); + toMove.setInBuildingID(msg.getTargetID()); + msg.setStartCoord(ZoneManager.convertWorldToLocal(building, toMove.getLoc())); + + if (toMove.getObjectType() == GameObjectType.PlayerCharacter) { + if (convertLocEnd.distanceSquared2D(toMove.getLoc()) > 6000 * 6000) { + + Logger.info("ENDLOC:" + convertLocEnd.x + ',' + convertLocEnd.y + ',' + convertLocEnd.z + + ',' + "GETLOC:" + toMove.getLoc().x + ',' + toMove.getLoc().y + ',' + toMove.getLoc().z + " Name " + ((PlayerCharacter) toMove).getCombinedName()); + toMove.teleport(toMove.getLoc()); + + return; + } + } + + startLocation = toMove.getLoc(); + endLocation = convertLocEnd; + + } else { + + toMove.setInBuilding(-1); + toMove.setInFloorID(-1); + toMove.setInBuildingID(0); + //SYNC PLAYER + toMove.teleport(toMove.getLoc()); + return; + } + + } else { + toMove.setInBuildingID(0); + toMove.setInFloorID(-1); + toMove.setInBuilding(-1); + msg.setStartCoord(toMove.getLoc()); + } + + //make sure we set the correct player. + msg.setSourceType(toMove.getObjectType().ordinal()); + msg.setSourceID(toMove.getObjectUUID()); + + //if player in region, modify location to local location of building. set target to building. + if (toMove.region != null) { + Building regionBuilding = Regions.GetBuildingForRegion(toMove.region); + if (regionBuilding != null) { + msg.setStartCoord(ZoneManager.convertWorldToLocal(Regions.GetBuildingForRegion(toMove.region), toMove.getLoc())); + msg.setEndCoord(ZoneManager.convertWorldToLocal(regionBuilding, endLocation)); + msg.setInBuilding(toMove.region.level); + msg.setUnknown01(toMove.region.room); + msg.setTargetType(GameObjectType.Building.ordinal()); + msg.setTargetID(regionBuilding.getObjectUUID()); + } + + } else { + toMove.setInBuildingID(0); + toMove.setInFloorID(-1); + toMove.setInBuilding(-1); + msg.setStartCoord(toMove.getLoc()); + msg.setEndCoord(endLocation); + msg.setTargetType(0); + msg.setTargetID(0); + } + + //checks sync between character and server, if out of sync, teleport player to original position and return. + if (toMove.getObjectType() == GameObjectType.PlayerCharacter) { + boolean startLocInSync = checkSync(toMove, startLocation, toMove.getLoc()); + + if (!startLocInSync) { + syncLoc(toMove, toMove.getLoc(), startLocInSync); + return; + } + + } + + // set direction, based on the current location which has just been sync'd + // with the client and the calc'd destination + toMove.setFaceDir(endLocation.subtract2D(toMove.getLoc()).normalize()); + + boolean collide = false; + if (toMove.getObjectType().equals(GameObjectType.PlayerCharacter)) { + Vector3fImmutable collidePoint = Bounds.PlayerBuildingCollisionPoint((PlayerCharacter) toMove, toMove.getLoc(), endLocation); + + if (collidePoint != null) { + msg.setEndCoord(collidePoint); + endLocation = collidePoint; + collide = true; + } + + } + + if (toMove.getObjectType() == GameObjectType.PlayerCharacter && ((PlayerCharacter) toMove).isTeleportMode()) { + toMove.teleport(endLocation); + return; + } + + // move to end location, this can interrupt the current move + toMove.setEndLoc(endLocation); + + // ChatManager.chatSystemInfo((PlayerCharacter)toMove, "Moving to " + Vector3fImmutable.toString(endLocation)); + + // make sure server knows player is not sitting + toMove.setSit(false); + + // cancel any effects that break upon movement + toMove.cancelOnMove(); + + //cancel any attacks for manual move. + if ((toMove.getObjectType() == GameObjectType.PlayerCharacter) && msg.getUnknown02() == 0) + toMove.setCombatTarget(null); + + + // If it's not a player moving just send the message + + if ((toMove.getObjectType() == GameObjectType.PlayerCharacter) == false) { + DispatchMessage.sendToAllInRange(toMove, msg); + return; + } + + // If it's a player who is moving then we need to handle characters + // who should see the message via group follow + + PlayerCharacter player = (PlayerCharacter) toMove; + + player.setTimeStamp("lastMoveGate", System.currentTimeMillis()); + + if (collide) + DispatchMessage.dispatchMsgToInterestArea(player, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + DispatchMessage.dispatchMsgToInterestArea(player, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + + + // Handle formation movement if needed + + if (player.getFollow() == false) + return; + + + City cityObject = null; + Zone serverZone = null; + + serverZone = ZoneManager.findSmallestZone(player.getLoc()); + cityObject = (City) DbManager.getFromCache(GameObjectType.City, serverZone.getPlayerCityUUID()); + + // Do not send group messages if player is on grid + + if (cityObject != null) + return; + + // If player is not in a group we can exit here + + Group group = GroupManager.getGroup(player); + + if (group == null) + return; + + // Echo group movement messages + + if (group.getGroupLead().getObjectUUID() == player.getObjectUUID()) + moveGroup(player, player.getClientConnection(), msg); + + } + + /** + * compare client and server location to verify that the two are in sync + * + * @param ac the player character + * @param clientLoc location as reported by the client + * @param serverLoc location known to the server + * @return true if the two are in sync + */ + private static boolean checkSync(AbstractCharacter ac, Vector3fImmutable clientLoc, Vector3fImmutable serverLoc) { + + float desyncDist = clientLoc.distanceSquared2D(serverLoc); + + // desync logging + if (MBServerStatics.MOVEMENT_SYNC_DEBUG) + if (desyncDist > MBServerStatics.MOVEMENT_DESYNC_TOLERANCE * MBServerStatics.MOVEMENT_DESYNC_TOLERANCE) + // our current location server side is a calc of last known loc + direction + speed and known time of last update + Logger.debug("Movement out of sync for " + ac.getFirstName() + + ", Server Loc: " + serverLoc.getX() + ' ' + serverLoc.getZ() + + " , Client loc: " + clientLoc.getX() + ' ' + clientLoc.getZ() + + " desync distance " + desyncDist + + " moving=" + ac.isMoving()); + else + Logger.debug("Movement sync is good - desyncDist = " + desyncDist); + + if (ac.getDebug(1) && ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + if (desyncDist > MBServerStatics.MOVEMENT_DESYNC_TOLERANCE * MBServerStatics.MOVEMENT_DESYNC_TOLERANCE) { + PlayerCharacter pc = (PlayerCharacter) ac; + ChatManager.chatSystemInfo(pc, + "Movement out of sync for " + ac.getFirstName() + + ", Server Loc: " + serverLoc.getX() + ' ' + serverLoc.getZ() + + " , Client loc: " + clientLoc.getX() + ' ' + clientLoc.getZ() + + " desync distance " + desyncDist + + " moving=" + ac.isMoving()); + } + + // return indicator that the two are in sync or not + return (desyncDist < 100f * 100f); + + } + + + public static void finishChangeAltitude(AbstractCharacter ac, float targetAlt) { + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) == false) + return; + + //reset the getLoc timer before we clear other timers + // otherwise the next call to getLoc will not be correct + ac.resetLastSetLocUpdate(); + + // call getLoc once as it processes loc to the ms + Vector3fImmutable curLoc = ac.getLoc(); + + if (MBServerStatics.MOVEMENT_SYNC_DEBUG) + Logger.info("Finished Alt change, setting the end location to " + + ac.getEndLoc().getX() + ' ' + ac.getEndLoc().getZ() + + " moving=" + ac.isMoving() + + " and current location is " + curLoc.getX() + ' ' + curLoc.getZ()); + + if (ac.getDebug(1) && ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + ChatManager.chatSystemInfo((PlayerCharacter) ac, "Finished Alt change, setting the end location to " + ac.getEndLoc().getX() + ' ' + ac.getEndLoc().getZ() + " moving=" + ac.isMoving() + " and current location is " + curLoc.getX() + ' ' + curLoc.getZ()); + + //Send run/walk/sit/stand to tell the client we are flying / landing etc + ac.update(); + ac.stopMovement(ac.getLoc()); + if (ac.isAlive()) + MovementManager.sendRWSSMsg(ac); + + //Check collision again + } + + + // Handle formation movement in group + + public static void moveGroup(PlayerCharacter pc, ClientConnection origin, MoveToPointMsg msg) throws MsgSendException { + // get forward vector + Vector3f faceDir = new Vector3f(pc.getFaceDir().x, 0, pc.getFaceDir().z).normalize(); + // get perpendicular vector + Vector3f crossDir = new Vector3f(faceDir.z, 0, -faceDir.x); + + //get source loc with altitude + Vector3f sLoc = new Vector3f(pc.getLoc().x, pc.getAltitude(), pc.getLoc().z); + + Group group = GroupManager.getGroup(pc); + Set members = group.getMembers(); + int pos = 0; + for (PlayerCharacter member : members) { + + if (member == null) + continue; + if (member.getObjectUUID() == pc.getObjectUUID()) + continue; + + MoveToPointMsg groupMsg = new MoveToPointMsg(msg); + + // Verify group member should be moved + + pos++; + if (member.getFollow() != true) + continue; + + //get member loc with altitude, then range against source loc + Vector3f mLoc = new Vector3f(member.getLoc().x, member.getAltitude(), member.getLoc().z); + + if (sLoc.distanceSquared2D(mLoc) > sqr(MBServerStatics.FORMATION_RANGE)) + continue; + + //don't move if player has taken damage from another player in last 60 seconds + long lastAttacked = System.currentTimeMillis() - pc.getLastPlayerAttackTime(); + if (lastAttacked < 60000) + continue; + + if (!member.isAlive()) + continue; + + //don't move if player is stunned or rooted + PlayerBonuses bonus = member.getBonuses(); + if (bonus.getBool(ModType.Stunned, SourceType.None) || bonus.getBool(ModType.CannotMove, SourceType.None)) + continue; + + member.update(); + + + // All checks passed, let's move the player + // First get the offset position + Vector3f offset = Formation.getOffset(group.getFormation(), pos); + Vector3fImmutable destination = pc.getEndLoc(); + // offset forwards or backwards + destination = destination.add(faceDir.mult(offset.z)); + // offset left or right + destination = destination.add(crossDir.mult(offset.x)); + // ArrayList awoList = WorldGrid.INSTANCE.getObjectsInRangePartial(member, member.getLoc().distance2D(destination) +1000, MBServerStatics.MASK_BUILDING); + // + // boolean skip = false; + // + // for (AbstractWorldObject awo: awoList){ + // Building building = (Building)awo; + // + // if (building.getBounds() != null){ + // if (Bounds.collide(building, member.getLoc(), destination)){ + // skip = true; + // break; + // } + // + // } + // + // } + // + // if (skip) + // continue; + // if (member.isMoving()) + // member.stopMovement(); + + // Update player speed to match group lead speed and make standing + if (member.isSit() || (member.isWalk() != pc.isWalk())) { + member.setSit(false); + member.setWalkMode(pc.isWalk()); + MovementManager.sendRWSSMsg(member); + } + + //cancel any effects that break upon movement + member.cancelOnMove(); + + // send movement for other players to see + groupMsg.setSourceID(member.getObjectUUID()); + groupMsg.setStartCoord(member.getLoc()); + groupMsg.setEndCoord(destination); + groupMsg.clearTarget(); + DispatchMessage.sendToAllInRange(member, groupMsg); + + // update group member + member.setFaceDir(destination.subtract2D(member.getLoc()).normalize()); + member.setEndLoc(destination); + } + } + + public static void translocate(AbstractCharacter teleporter, Vector3fImmutable targetLoc, Regions region) { + + + if (targetLoc == null) + return; + + Vector3fImmutable oldLoc = new Vector3fImmutable(teleporter.getLoc()); + + teleporter.stopMovement(targetLoc); + teleporter.setRegion(region); + + //mobs ignore region sets for now. + if (teleporter.getObjectType().equals(GameObjectType.Mob)) { + teleporter.setInBuildingID(0); + teleporter.setInBuilding(-1); + teleporter.setInFloorID(-1); + TeleportToPointMsg msg = new TeleportToPointMsg(teleporter, targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0, -1, -1); + DispatchMessage.dispatchMsgToInterestArea(oldLoc, teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + return; + } + TeleportToPointMsg msg = new TeleportToPointMsg(teleporter, targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0, -1, -1); + //we shouldnt need to send teleport message to new area, as loadjob should pick it up. + // DispatchMessage.dispatchMsgToInterestArea(teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + DispatchMessage.dispatchMsgToInterestArea(oldLoc, teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + if (teleporter.getObjectType().equals(GameObjectType.PlayerCharacter)) + InterestManager.INTERESTMANAGER.HandleLoadForTeleport((PlayerCharacter) teleporter); + + } + + private static void syncLoc(AbstractCharacter ac, Vector3fImmutable clientLoc, boolean useClientLoc) { + ac.teleport(ac.getLoc()); + } +} diff --git a/src/engine/gameManager/NPCManager.java b/src/engine/gameManager/NPCManager.java new file mode 100644 index 00000000..11deb263 --- /dev/null +++ b/src/engine/gameManager/NPCManager.java @@ -0,0 +1,373 @@ +package engine.gameManager; + +import engine.Enum; +import engine.InterestManagement.WorldGrid; +import engine.math.Quaternion; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.PetMsg; +import engine.objects.*; +import engine.powers.EffectsBase; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.acos; + +public enum NPCManager { + + NPC_MANAGER; + public static HashMap> _runeSetMap = new HashMap<>(); + + public static void LoadAllRuneSets() { + _runeSetMap = DbManager.ItemBaseQueries.LOAD_RUNES_FOR_NPC_AND_MOBS(); + } + + public static void LoadAllBootySets() { + LootManager._bootySetMap = DbManager.LootQueries.LOAD_BOOTY_TABLES(); + } + + public static void applyRuneSetEffects(Mob mob) { + + // Early exit + + if (mob.runeSet == 0) + return; + + //Apply all rune effects. + + if (NPCManager._runeSetMap.get(mob.runeSet).contains(252623)) { + mob.isPlayerGuard = true; + } + + // Only captains have contracts + + if (mob.contract != null || mob.isPlayerGuard) + applyEffectsForRune(mob, 252621); + + + // Apply effects from RuneSet + + if (mob.runeSet != 0) + for (int runeID : _runeSetMap.get(mob.runeSet)) + applyEffectsForRune(mob, runeID); + + // Not sure why but apply Warrior effects for some reason? + + applyEffectsForRune(mob, 2518); + } + + public static void applyEffectsForRune(AbstractCharacter character, int runeID) { + + EffectsBase effectsBase; + RuneBase sourceRune = RuneBase.getRuneBase(runeID); + + // Race runes are in the runeset but not in runebase for some reason + + if (sourceRune == null) + return; + + for (MobBaseEffects mbe : sourceRune.getEffectsList()) { + + effectsBase = PowersManager.getEffectByToken(mbe.getToken()); + + if (effectsBase == null) { + Logger.info("Mob: " + character.getObjectUUID() + " EffectsBase Null for Token " + mbe.getToken()); + continue; + } + + //check to upgrade effects if needed. + if (character.effects.containsKey(Integer.toString(effectsBase.getUUID()))) { + + if (mbe.getReqLvl() > (int) character.level) + continue; + + Effect eff = character.effects.get(Integer.toString(effectsBase.getUUID())); + + if (eff == null) + continue; + + //Current effect is a higher rank, dont apply. + if (eff.getTrains() > mbe.getRank()) + continue; + + //new effect is of a higher rank. remove old effect and apply new one. + eff.cancelJob(); + character.addEffectNoTimer(Integer.toString(effectsBase.getUUID()), effectsBase, mbe.getRank(), true); + } else { + + if (mbe.getReqLvl() > (int) character.level) + continue; + + character.addEffectNoTimer(Integer.toString(effectsBase.getUUID()), effectsBase, mbe.getRank(), true); + } + + } + } + + public static void dismissNecroPet(Mob necroPet, boolean updateOwner) { + + necroPet.setCombatTarget(null); + necroPet.hasLoot = false; + + if (necroPet.parentZone != null) + necroPet.parentZone.zoneMobSet.remove(necroPet); + + try { + necroPet.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + necroPet.playerAgroMap.clear(); + WorldGrid.RemoveWorldObject(necroPet); + + DbManager.removeFromCache(necroPet); + + PlayerCharacter petOwner = necroPet.getOwner(); + + if (petOwner != null) { + necroPet.setOwner(null); + petOwner.setPet(null); + + if (updateOwner == false) + return; + + PetMsg petMsg = new PetMsg(5, null); + Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } + } + + public static void auditNecroPets(PlayerCharacter player) { + int removeIndex = 0; + while (player.necroPets.size() >= 10) { + + + if (removeIndex == player.necroPets.size()) + break; + + Mob necroPet = player.necroPets.get(removeIndex); + + if (necroPet == null) { + removeIndex++; + continue; + } + dismissNecroPet(necroPet, true); + player.necroPets.remove(necroPet); + removeIndex++; + + + } + } + + public static void resetNecroPets(PlayerCharacter player) { + + for (Mob necroPet : player.necroPets) + if (necroPet.isPet()) + necroPet.agentType = Enum.AIAgentType.MOBILE; + } + + public static void spawnNecroPet(PlayerCharacter playerCharacter, Mob mob) { + + if (mob == null) + return; + + if (mob.getMobBaseID() != 12021 && mob.getMobBaseID() != 12022) + return; + + auditNecroPets(playerCharacter); + resetNecroPets(playerCharacter); + + playerCharacter.necroPets.add(mob); + } + + public static void dismissNecroPets(PlayerCharacter playerCharacter) { + + + if (playerCharacter.necroPets.isEmpty()) + return; + + for (Mob necroPet : playerCharacter.necroPets) { + + try { + dismissNecroPet(necroPet, true); + } catch (Exception e) { + Logger.error(e); + } + } + playerCharacter.necroPets.clear(); + } + + + public static void removeSiegeMinions(Mob mobile) { + + for (Mob toRemove : mobile.siegeMinionMap.keySet()) { + + if (mobile.isMoving()) { + + mobile.stopMovement(mobile.getLoc()); + + if (toRemove.parentZone != null) + toRemove.parentZone.zoneMobSet.remove(toRemove); + } + + try { + toRemove.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + if (toRemove.parentZone != null) + toRemove.parentZone.zoneMobSet.remove(toRemove); + + WorldGrid.RemoveWorldObject(toRemove); + WorldGrid.removeObject(toRemove); + DbManager.removeFromCache(toRemove); + + PlayerCharacter petOwner = toRemove.getOwner(); + + if (petOwner != null) { + + petOwner.setPet(null); + toRemove.setOwner(null); + + PetMsg petMsg = new PetMsg(5, null); + Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } + } + } + + public static boolean removeMobileFromBuilding(Mob mobile, Building building) { + + // Remove npc from it's building + + try { + mobile.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + if (mobile.parentZone != null) + mobile.parentZone.zoneMobSet.remove(mobile); + + if (building != null) { + building.getHirelings().remove(mobile); + removeSiegeMinions(mobile); + } + + // Delete npc from database + + if (DbManager.MobQueries.DELETE_MOB(mobile) == 0) + return false; + + // Remove npc from the simulation + + mobile.removeFromCache(); + DbManager.removeFromCache(mobile); + WorldGrid.RemoveWorldObject(mobile); + WorldGrid.removeObject(mobile); + return true; + } + + public static void loadAllPirateNames() { + + DbManager.NPCQueries.LOAD_PIRATE_NAMES(); + } + + public static String getPirateName(int mobBaseID) { + + ArrayList nameList = null; + + // If we cannot find name for this mobbase then + // fallback to human male + + if (NPC._pirateNames.containsKey(mobBaseID)) + nameList = NPC._pirateNames.get(mobBaseID); + else + nameList = NPC._pirateNames.get(2111); + + if (nameList == null) { + Logger.error("Null name list for 2111!"); + } + + return nameList.get(ThreadLocalRandom.current().nextInt(nameList.size())); + + } + + public static ArrayList getProtectedBuildings(NPC npc) { + + ArrayList protectedBuildings = new ArrayList<>(); + + if (npc.building == null) + return protectedBuildings; + + if (npc.building.getCity() == null) + return protectedBuildings; + + for (Building b : npc.building.getCity().getParent().zoneBuildingSet) { + + if (b.getBlueprint() == null) + continue; + + if (b.getProtectionState().equals(Enum.ProtectionState.CONTRACT)) + protectedBuildings.add(b); + + if (b.getProtectionState().equals(Enum.ProtectionState.PENDING)) + protectedBuildings.add(b); + } + + return protectedBuildings; + } + + public static int slotCharacterInBuilding(AbstractCharacter abstractCharacter) { + + int buildingSlot; + + if (abstractCharacter.building == null) + return -1; + + // Get next available slot for this NPC and use it + // to add the NPC to the building's hireling list + // Account for R8's having slots reversed. + + if (abstractCharacter.building.getBlueprint() != null && abstractCharacter.building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.TOL) && abstractCharacter.building.getRank() == 8) + buildingSlot = BuildingManager.getLastAvailableSlot(abstractCharacter.building); + else + buildingSlot = BuildingManager.getAvailableSlot(abstractCharacter.building); + + if (buildingSlot == -1) + Logger.error("No available slot for NPC: " + abstractCharacter.getObjectUUID()); + + abstractCharacter.building.getHirelings().put(abstractCharacter, buildingSlot); + + // Override bind and location for this npc derived + // from BuildingManager slot location data. + + Vector3fImmutable slotLocation = BuildingManager.getSlotLocation(abstractCharacter.building, buildingSlot).getLocation(); + + abstractCharacter.bindLoc = abstractCharacter.building.getLoc().add(slotLocation); + + // Rotate slot position by the building rotation + + abstractCharacter.bindLoc = Vector3fImmutable.rotateAroundPoint(abstractCharacter.building.getLoc(), abstractCharacter.bindLoc, abstractCharacter.building.getBounds().getQuaternion().angleY); + + abstractCharacter.loc = new Vector3fImmutable(abstractCharacter.bindLoc); + + // Rotate NPC rotation by the building's rotation + + Quaternion slotRotation = new Quaternion().fromAngles(0, acos(abstractCharacter.getRot().y) * 2, 0); + slotRotation = slotRotation.mult(abstractCharacter.building.getBounds().getQuaternion()); + abstractCharacter.setRot(new Vector3f(0, slotRotation.y, 0)); + + // Configure region and floor/level for this NPC + + abstractCharacter.region = BuildingManager.GetRegion(abstractCharacter.building, abstractCharacter.bindLoc.x, abstractCharacter.bindLoc.y, abstractCharacter.bindLoc.z); + + return buildingSlot; + } +} diff --git a/src/engine/gameManager/PowersManager.java b/src/engine/gameManager/PowersManager.java new file mode 100644 index 00000000..9e15abc2 --- /dev/null +++ b/src/engine/gameManager/PowersManager.java @@ -0,0 +1,2714 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum.*; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.WorldGrid; +import engine.db.handlers.dbEffectsBaseHandler; +import engine.db.handlers.dbPowerHandler; +import engine.db.handlers.dbSkillReqHandler; +import engine.job.AbstractJob; +import engine.job.AbstractScheduleJob; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.*; +import engine.math.Vector3fImmutable; +import engine.net.ByteBufferWriter; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import engine.powers.*; +import engine.powers.poweractions.AbstractPowerAction; +import engine.powers.poweractions.TrackPowerAction; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; + +public enum PowersManager { + + POWERMANAGER; + + public static HashMap powersBaseByIDString = new HashMap<>(); + public static HashMap powersBaseByToken = new HashMap<>(); + public static HashMap effectsBaseByIDString = new HashMap<>(); + public static HashMap effectsBaseByToken = new HashMap<>(); + public static HashMap powerActionsByIDString = new HashMap<>(); + public static HashMap powerActionsByID = new HashMap<>(); + public static HashMap ActionTokenByIDString = new HashMap<>(); + public static HashMap AnimationOverrides = new HashMap<>(); + public static HashMap> AllMobPowers; + private static JobScheduler js; + + private PowersManager() { + + } + + public static void initPowersManager(boolean fullPowersLoad) { + + if (fullPowersLoad) + PowersManager.InitializePowers(); + else + PowersManager.InitializeLoginPowers(); + + PowersManager.js = JobScheduler.getInstance(); + + } + + public static PowersBase getPowerByToken(int token) { + return PowersManager.powersBaseByToken.get(token); + } + + public static PowersBase getPowerByIDString(String IDString) { + return PowersManager.powersBaseByIDString.get(IDString); + } + + public static EffectsBase getEffectByIDString(String IDString) { + return PowersManager.effectsBaseByIDString.get(IDString); + } + + public static AbstractPowerAction getPowerActionByIDString(String IDString) { + return PowersManager.powerActionsByIDString.get(IDString); + } + + public static EffectsBase getEffectByToken(int token) { + return PowersManager.effectsBaseByToken.get(token); + } + + // This pre-loads only PowersBase for login + public static void InitializeLoginPowers() { + + // get all PowersBase + ArrayList pbList = dbSkillReqHandler.getAllPowersBase(); + + for (PowersBase pb : pbList) { + if (pb.getToken() != 0) + PowersManager.powersBaseByToken.put(pb.getToken(), pb); + } + } + + // This pre-loads all powers and effects + public static void InitializePowers() { + + // Add EffectsBase + ArrayList ebList = dbEffectsBaseHandler.getAllEffectsBase(); + + for (EffectsBase eb : ebList) { + PowersManager.effectsBaseByToken.put(eb.getToken(), eb); + PowersManager.effectsBaseByIDString.put(eb.getIDString(), eb); + + } + + // Add Fail Conditions + EffectsBase.getFailConditions(PowersManager.effectsBaseByIDString); + + // Add Modifiers to Effects + dbEffectsBaseHandler.cacheAllEffectModifiers(); + + // Add Source Types to Effects + dbPowerHandler.addAllSourceTypes(); + dbPowerHandler.addAllAnimationOverrides(); + + // Add PowerActions + AbstractPowerAction.getAllPowerActions(PowersManager.powerActionsByIDString, PowersManager.powerActionsByID, PowersManager.effectsBaseByIDString); + + // Load valid Item Flags + // AbstractPowerAction.loadValidItemFlags(PowersManager.powerActionsByIDString); + + // get all PowersBase + ArrayList pbList = dbSkillReqHandler.getAllPowersBase(); + for (PowersBase pb : pbList) { + if (pb.getToken() != 0) { + PowersManager.powersBaseByIDString.put(pb.getIDString(), pb); + PowersManager.powersBaseByToken.put(pb.getToken(), pb); + } + } + + // Add Power Prereqs + PowerPrereq.getAllPowerPrereqs(PowersManager.powersBaseByIDString); + // Add Fail Conditions + dbSkillReqHandler.getFailConditions(PowersManager.powersBaseByIDString); + // Add Actions Base + ActionsBase.getActionsBase(PowersManager.powersBaseByIDString, + PowersManager.powerActionsByIDString); + + } + + public static EffectsBase setEffectToken(int ID, int token) { + for (EffectsBase eb : PowersManager.effectsBaseByIDString.values()) { + if (eb.getUUID() == ID) { + eb.setToken(token); + return eb; + } + } + return null; + } + + public static void usePower(final PerformActionMsg msg, ClientConnection origin, + boolean sendCastToSelf) { + + if (usePowerA(msg, origin, sendCastToSelf)) { + // Cast failed for some reason, reset timer + + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + // Send Fail to cast message + PlayerCharacter pc = SessionManager + .getPlayerCharacter(origin); + + if (pc != null) { + sendPowerMsg(pc, 2, msg); + if (pc.isCasting()) { + pc.update(); + } + + pc.setIsCasting(false); + } + + } + } + + public static void useMobPower(Mob caster, AbstractCharacter target, PowersBase pb, int rank) { + + PerformActionMsg msg = createPowerMsg(pb, rank, caster, target); + msg.setUnknown04(1); + + if (useMobPowerA(msg, caster)) { + //sendMobPowerMsg(caster,2,msg); //Lol wtf was i thinking sending msg's to mobs... ZZZZ + } + } + + public static boolean usePowerA(final PerformActionMsg msg, ClientConnection origin, + boolean sendCastToSelf) { + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter( + origin); + if (playerCharacter == null) + return false; + + boolean CSRCast = false; + + + if (MBServerStatics.POWERS_DEBUG) { + ChatManager.chatSayInfo( + playerCharacter, + "Using Power: " + Integer.toHexString(msg.getPowerUsedID()) + + " (" + msg.getPowerUsedID() + ')'); + Logger.info("Using Power: " + + Integer.toHexString(msg.getPowerUsedID()) + " (" + + msg.getPowerUsedID() + ')'); + } + + //Sending recycle message to player if died while casting. + if (!playerCharacter.isAlive() && msg.getPowerUsedID() != 428589216) { //succor + + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); + Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + return false; + } + + + // if (!pc.getPowers().contains(msg.getPowerUsedID())) { + // sendPowerMsg(pc, 10, msg); + // return false; + // } + // verify recycle timer is not active for power, skip for a CSR + if (playerCharacter.getRecycleTimers().containsKey(msg.getPowerUsedID()) && !playerCharacter.isCSR()) { + // ChatManager.chatSayInfo(pc, "Recycle timer not finished!"); + + Logger.warn("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' recycle timer not finished " + playerCharacter.getName()); + return false; + } + + // get power + PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); + if (pb == null) { + ChatManager.chatSayInfo(playerCharacter, + "This power is not implemented yet."); + + // Logger.error("usePowerA(): Power '" + + // msg.getPowerUsedID() + // + "' was not found on powersBaseByToken map."); + return true; + // return false; + } + + if (playerCharacter.getLastPower() != null) + return true; + + //Check if Power Target is allowed to cast. + + + // Check powers for normal users + if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerUsedID())) + if (!playerCharacter.isCSR()) { + if (!MBServerStatics.POWERS_DEBUG) { + // ChatManager.chatSayInfo(pc, "You may not cast that spell!"); + + Logger.info("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' was not associated with " + playerCharacter.getName()); + return true; + } + } else + CSRCast = true; + + // get numTrains for power + int trains = msg.getNumTrains(); + + // can't go over the max trains for the power, unless CSR + if (trains > pb.getMaxTrains() && !playerCharacter.isCSR()) { + trains = pb.getMaxTrains(); + msg.setNumTrains(trains); + } + + // can't go over total trains by player + if (playerCharacter.getPowers() != null && playerCharacter.getPowers().containsKey(msg.getPowerUsedID())) { + CharacterPower cp = playerCharacter.getPowers().get(msg.getPowerUsedID()); + if (cp != null) { + int tot = cp.getTotalTrains(); + if (tot == 0 && !playerCharacter.isCSR()) + return false; + if (trains != tot && !playerCharacter.isCSR()) { + trains = tot; + msg.setNumTrains(trains); + } + } + } + + // get recycle time in ms + int time = pb.getRecycleTime(trains); + + // verify player is in correct mode (combat/nonCombat) + if (playerCharacter.isCombat()) { + if (!pb.allowedInCombat()) + // ChatManager.chatPowerError(pc, + // "This power is not allowed in combat mode."); + return true; + } else if (!pb.allowedOutOfCombat()) + // ChatManager.chatPowerError(pc, + // "You must be in combat mode to use this power."); + return true; + + // verify player is not stunned or prohibited from casting + PlayerBonuses bonus = playerCharacter.getBonuses(); + SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); + if (bonus != null && (bonus.getBool(ModType.Stunned, SourceType.None) || bonus.getBool(ModType.CannotCast, SourceType.None) || bonus.getBool(ModType.BlockedPowerType, sourceType))) + return true; + + // if moving make sure spell valid for movement + Vector3fImmutable endLoc = playerCharacter.getEndLoc(); + + + if (!pb.canCastWhileMoving()) + if (playerCharacter.isMoving()) { + + // if movement left is less then 1 seconds worth then let cast + // go through. + float distanceLeftSquared = endLoc.distanceSquared2D(playerCharacter.getLoc()); + + if (distanceLeftSquared > sqr(playerCharacter.getSpeed())) + return true; + } + // if flying, make sure spell valid for flying. + // if (pc.getAltitude() > 0) + // if (!pb.canCastWhileFlying()) + // return true; + + int targetLiveCounter = -1; + + // get target based on targetType; + if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target + if (pb.isAOE()) { + if (!pb.usePointBlank()) { + AbstractWorldObject target = getTarget(msg); + + + if (target != null && target.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) { + PowersManager.sendPowerMsg(playerCharacter, 9, new PerformActionMsg(msg)); + return true; + } + + if (target == null) { + if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb + .getRange())) + return true; + } else if (verifyInvalidRange(playerCharacter, target, pb.getRange())) + // pc.getLoc().distance(target.getLoc()) > + // pb.getRange()) + return true; + + + } + } else { + // get target + AbstractWorldObject target = getTarget(msg); + + if (target == null) + return true; + + if (!target.isAlive() && target.getObjectType().equals(GameObjectType.Building) == false && msg.getPowerUsedID() != 428589216) + return true; + + float range = pb.getRange(); + // verify target is in range + + + if (verifyInvalidRange(playerCharacter, target, range)) + // (pc.getLoc().distance(target.getLoc()) > pb.getRange()) { + // TODO send message that target is out of range + return true; + + // verify target is valid type + if (!validateTarget(target, playerCharacter, pb)) + return true; + + + if (AbstractWorldObject.IsAbstractCharacter(target)) + targetLiveCounter = ((AbstractCharacter) target).getLiveCounter(); + } + + // verify regular player can cast spell, otherwise authenticate + if (!pb.regularPlayerCanCast()) { + Account a = SessionManager.getAccount(playerCharacter); + if (a.status.equals(AccountStatus.ADMIN) == false) { + Logger.warn("Player '" + playerCharacter.getName() + + "' is attempting to cast a spell outside of their own access level."); + return true; + } + } + + // verify player has proper effects applied to use power + if (pb.getEffectPrereqs().size() > 0 && playerCharacter.getEffects() != null) { + + boolean passed = false; + for (PowerPrereq pp : pb.getEffectPrereqs()) { + + EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect()); + + if (playerCharacter.containsEffect(eb.getToken())) { + passed = true; + break; + } + } + + if (!passed) + return true; + } + + //verify player has proper equipment to use power + if (pb.getEquipPrereqs().size() > 0) { + + boolean passed = false; + + for (PowerPrereq pp : pb.getEquipPrereqs()) { + + int slot = pp.mainHand() ? MBServerStatics.SLOT_MAINHAND : MBServerStatics.SLOT_OFFHAND; + + if (playerCharacter.validEquip(slot, pp.getMessage())) { + passed = true; //should have item in slot + break; + } else if (!pp.isRequired()) { + passed = true; //should not have item in slot + break; + } + } + if (!passed) + return true; + } + + // TODO if target immune to powers, cancel unless aoe + // Also make sure No.ImmuneToPowers is false for target + // if there's a different power waiting to finish, stop here + + + //get power cost and calculate any power cost modifiers + float cost = pb.getCost(trains); + + if (playerCharacter.isCSR()) + cost = 0; + + if (bonus != null) + cost *= (1 + bonus.getFloatPercentAll(ModType.PowerCost, SourceType.None)); + + if (playerCharacter.getAltitude() > 0) + cost *= 1.5f; + + // Verify player can afford to use power + //CCR toons dont use cost + + if (!playerCharacter.isCSR()) { + if (cost > 0) + if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0) + if (playerCharacter.getHealth() <= cost) + return true; + else { + playerCharacter.modifyHealth(-cost, playerCharacter, true); + ModifyHealthMsg mhm = new ModifyHealthMsg(playerCharacter, playerCharacter, -cost, + 0f, 0f, 0, null, + 9999, 0); + mhm.setOmitFromChat(1); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, mhm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + else if (pb.useMana()) + if (playerCharacter.getMana() < cost) + return true; + else + playerCharacter.modifyMana(-cost, playerCharacter, true); + else if (pb.useStamina()) + if (playerCharacter.getStamina() < cost) + return true; + else + playerCharacter.modifyStamina(-cost, playerCharacter, true); + else if (playerCharacter.getHealth() <= cost) + return true; + else + playerCharacter.modifyHealth(-cost, playerCharacter, true); + } + + + // Validity checks passed, move on to casting spell + //get caster's live counter + int casterLiveCounter = playerCharacter.getLiveCounter(); + + // run recycle job for when cast is available again, don't bother adding the timer for CSRs + if (time > 0) { + FinishRecycleTimeJob frtj = new FinishRecycleTimeJob(playerCharacter, msg); + playerCharacter.getRecycleTimers().put(msg.getPowerUsedID(), js.scheduleJob(frtj, time)); + } else { + // else send recycle message to unlock power + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); + Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + //what the fuck? +// // Send cast to other players +// if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0) +// msg.setUnknown04(2); // Vampire Race, use health? +// else +// msg.setUnknown04(1); // Regular Race, use mana? + int tr = msg.getNumTrains(); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, sendCastToSelf, false); + + //Make new msg.. + PerformActionMsg copyMsg = new PerformActionMsg(msg); + copyMsg.setNumTrains(tr); + + // make person casting stand up if spell (unless they're casting a chant which does not make them stand up) + if (pb.isSpell() && !pb.isChant() && playerCharacter.isSit()) { + playerCharacter.update(); + playerCharacter.setSit(false); + UpdateStateMsg updateStateMsg = new UpdateStateMsg(playerCharacter); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + } + + // update cast (use skill) fail condition + playerCharacter.cancelOnCast(); + + // update castSpell (use spell) fail condition if spell + if (pb.isSpell()) + playerCharacter.cancelOnSpell(); + + // get cast time in ms. + time = pb.getCastTime(trains); + + // set player is casting for regens + + + if (time > 100) { + playerCharacter.update(); + playerCharacter.setIsCasting(true); + } + + + playerCharacter.setLastMovementState(playerCharacter.getMovementState()); + + // run timer job to end cast + if (time < 1) // run immediately + finishUsePower(copyMsg, playerCharacter, casterLiveCounter, targetLiveCounter); + else { + UsePowerJob upj = new UsePowerJob(playerCharacter, copyMsg, copyMsg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter); + JobContainer jc = js.scheduleJob(upj, time); + + // make lastPower + playerCharacter.setLastPower(jc); + } + + if (CSRCast) + Logger.info("CSR " + playerCharacter.getName() + " cast power " + msg.getPowerUsedID() + '.'); + + return false; + } + + public static void testPowers(ByteBufferWriter writer) { + writer.putInt(powersBaseByToken.size()); + for (int token : powersBaseByToken.keySet()) { + writer.putInt(token); + writer.putInt(40); + } + } + + public static boolean useMobPowerA(PerformActionMsg msg, Mob caster) { + if (caster == null) + return false; + + + if (!caster.isAlive() && msg.getPowerUsedID() != 428589216) //succor + return false; + + // get power + PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); + if (pb == null) + return true; + + // Check powers for normal users + // get numTrains for power + int trains = msg.getNumTrains(); + + // can't go over the max trains for the power, unless CSR + // can't go over total trains by player + // get recycle time in ms + int time = pb.getRecycleTime(trains); + + // verify player is in correct mode (combat/nonCombat) + // verify player is not stunned or prohibited from casting + PlayerBonuses bonus = caster.getBonuses(); + SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); + if (bonus != null && (bonus.getBool(ModType.Stunned, SourceType.None) || bonus.getBool(ModType.CannotCast, SourceType.None) || bonus.getBool(ModType.BlockedPowerType, sourceType))) + return true; + + // if moving make sure spell valid for movement + // if flying, make sure spell valid for flying. + // if (pc.getAltitude() > 0) + // if (!pb.canCastWhileFlying()) + // return true; + int targetLiveCounter = -1; + + // get target based on targetType; + if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target + if (pb.isAOE()) { + if (!pb.usePointBlank()) { + AbstractWorldObject target = getTarget(msg); + + + if (target == null) { + + if (caster.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb + .getRange())) + return true; + } else if (verifyInvalidRange(caster, target, pb.getRange())) + // pc.getLoc().distance(target.getLoc()) > + // pb.getRange()) + return true; + } + } else { + // get target + AbstractWorldObject target = getTarget(msg); + + if (target == null) + return true; + + // verify target is in range + if (verifyInvalidRange(caster, target, pb.getRange())) + // (pc.getLoc().distance(target.getLoc()) > pb.getRange()) { + // TODO send message that target is out of range + return true; + + // verify target is valid type + if (AbstractWorldObject.IsAbstractCharacter(target)) + targetLiveCounter = ((AbstractCharacter) target).getLiveCounter(); + } + + // TODO if target immune to powers, cancel unless aoe + // Also make sure No.ImmuneToPowers is false for target + // if there's a different power waiting to finish, stop here + if (caster.getLastMobPowerToken() != 0) + return true; + + //get power cost and calculate any power cost modifiers + // Validity checks passed, move on to casting spell + //get caster's live counter + int casterLiveCounter = caster.getLiveCounter(); + + // run recycle job for when cast is available again, don't bother adding the timer for CSRs + // Send cast to other players + if (caster.getObjectTypeMask() == MBServerStatics.MASK_UNDEAD) + msg.setUnknown05(0); // Regular Race, use mana? + else + msg.setUnknown05(0); + + int tr = msg.getNumTrains(); + + msg.setNumTrains(9999); + + DispatchMessage.sendToAllInRange(caster, msg); + DispatchMessage.sendToAllInRange(caster, msg); + + msg.setNumTrains(tr); + + // make person casting stand up if spell (unless they're casting a chant which does not make them stand up) + // update cast (use skill) fail condition + caster.cancelOnCast(); + + // update castSpell (use spell) fail condition if spell + if (pb.isSpell()) + caster.cancelOnSpell(); + + // get cast time in ms. + time = pb.getCastTime(trains); + + // set player is casting for regens + caster.setIsCasting(true); + caster.setLastMobPowerToken(pb.getToken()); + + // run timer job to end cast + if (time < 1 || pb.getToken() == -1994153779) { + // run immediately + finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter); + caster.setLastMobPowerToken(0); + } else { + caster.setLastMobPowerToken(pb.getToken()); + caster.setTimeStamp("FinishCast", System.currentTimeMillis() + (pb.getCastTime(trains))); + } + // finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter); // UseMobPowerJob upj = new UseMobPowerJob(caster, msg, msg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter); + // JobContainer jc = js.scheduleJob(upj, time); + // // make lastPower + + + return false; + } + + // called when a spell finishes casting. perform actions + public static void finishUsePower(final PerformActionMsg msg, PlayerCharacter playerCharacter, int casterLiveCounter, int targetLiveCounter) { + + PerformActionMsg performActionMsg; + Dispatch dispatch; + + if (playerCharacter == null || msg == null) + return; + + if (playerCharacter.isCasting()) { + playerCharacter.update(); + playerCharacter.updateStamRegen(-100); + } + + playerCharacter.resetLastSetLocUpdate(); + playerCharacter.setIsCasting(false); + // can't go over total trains by player + + + if (!playerCharacter.isAlive() || playerCharacter.getLiveCounter() != casterLiveCounter) { + playerCharacter.clearLastPower(); + finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true); + + + // Let's do good OO. Clone message don't modify it. + + performActionMsg = new PerformActionMsg(msg); + performActionMsg.setNumTrains(9999); + performActionMsg.setUnknown04(2); + + dispatch = Dispatch.borrow(playerCharacter, performActionMsg); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, performActionMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + return; + } + + // set player is not casting for regens + + + // clear power. + playerCharacter.clearLastPower(); + + PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); + + if (pb == null) { + Logger.error( + "finishUsePower(): Power '" + msg.getPowerUsedID() + + "' was not found on powersBaseByToken map."); + return; + } + + int trains = msg.getNumTrains(); + + // verify player is not stunned or power type is blocked + PlayerBonuses bonus = playerCharacter.getBonuses(); + + if (bonus != null) { + if (bonus.getBool(ModType.Stunned, SourceType.None)) + return; + + SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); + if (bonus.getBool(ModType.BlockedPowerType, sourceType)) { + finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true); + return; + } + } + + // get target loc + Vector3fImmutable targetLoc = msg.getTargetLoc(); + + + if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target + if (pb.isAOE()) { + if (!pb.usePointBlank()) { + AbstractWorldObject mainTarget = getTarget(msg); + + float speedRange = 0; + if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) { + speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f); + } + + if (mainTarget != null && mainTarget.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) { + PowersManager.sendPowerMsg(playerCharacter, 8, new PerformActionMsg(msg)); + return; + } + + if (mainTarget == null) { + + if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb + .getRange())) { + sendPowerMsg(playerCharacter, 8, msg); + return; + } + } else if (verifyInvalidRange(playerCharacter, mainTarget, speedRange + pb.getRange())) { + + sendPowerMsg(playerCharacter, 8, msg); + return; + } + } + } else { + + // get target + AbstractWorldObject mainTarget = getTarget(msg); + + if (mainTarget == null) + return; + + float speedRange = 0; + if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) { + speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f); + } + float range = pb.getRange() + speedRange; + + + if (verifyInvalidRange(playerCharacter, mainTarget, range)) { + + sendPowerMsg(playerCharacter, 8, msg); + return; + } + // (pc.getLoc().distance(target.getLoc()) > pb.getRange()) { + // TODO send message that target is out of range + + + } + + if (targetLoc.x == 0f || targetLoc.z == 0f) { + AbstractWorldObject tar = getTarget(msg); + if (tar != null) + targetLoc = tar.getLoc(); + } + + + // get list of targets + HashSet allTargets = getAllTargets( + getTarget(msg), msg.getTargetLoc(), playerCharacter, pb); + + // no targets found. send error message + + if (allTargets.size() == 0) { + sendPowerMsg(playerCharacter, 9, msg); + return; + } + + playerCharacter.setHateValue(pb.getHateValue(trains)); + + //Send Cast Message. +// PerformActionMsg castMsg = new PerformActionMsg(msg); +// castMsg.setNumTrains(9999); +// castMsg.setUnknown04(3); +// DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); +// + boolean msgCasted = false; + + for (AbstractWorldObject target : allTargets) { + + if (target == null) + continue; + + //Hacky Pyschic healing cross heal + + //make sure target hasn't respawned since we began casting + //skip this if liveCounter = -1 (from aoe) + + if (targetLiveCounter != -1) + if (AbstractWorldObject.IsAbstractCharacter(target)) + if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter()) + continue; + + if (!target.isAlive() && target.getObjectType() != GameObjectType.Building && pb.getToken() != 428589216 && pb.getToken() != 429425915) + continue; + + //make sure mob is awake to respond. + //if (target instanceof AbstractIntelligenceAgent) + //((AbstractIntelligenceAgent)target).enableIntelligence(); + // If Hit roll required, test hit + + boolean miss = false; + if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(playerCharacter, target, pb, msg)) { + miss = true; + //aggro mob even on a miss + if (target.getObjectType() == GameObjectType.Mob) { + Mob mobTarget = (Mob) target; + if (pb.isHarmful()) + mobTarget.handleDirectAggro(playerCharacter); + } + continue; + } + if (target.getObjectType() == GameObjectType.Mob) { + Mob mobTarget = (Mob) target; + if (pb.isHarmful()) + mobTarget.handleDirectAggro(playerCharacter); + } + //Power is aiding a target, handle aggro if combat target is a Mob. + if (!pb.isHarmful() && target.getObjectType() == GameObjectType.PlayerCharacter) { + PlayerCharacter pcTarget = (PlayerCharacter) target; + if (!pb.isHarmful()) + Mob.HandleAssistedAggro(playerCharacter, pcTarget); + } + + // update target of used power timer + + if (pb.isHarmful()) + if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectUUID() != playerCharacter.getObjectUUID()) { + ((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); + playerCharacter.setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); + } + + + //Player didn't miss power, send finish cast. Miss cast already sent. + + + // finally Apply actions + for (ActionsBase ab : pb.getActions()) { + // get numTrains for power, skip action if invalid + + if (trains < ab.getMinTrains() || trains > ab.getMaxTrains()) + continue; + // If something blocks the action, then stop + + if (ab.blocked(target, pb, trains)) { + + PowersManager.sendEffectMsg(playerCharacter, 5, ab, pb); + continue; + } + + // TODO handle overwrite stack order here + String stackType = ab.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; + // if (!stackType.equals("IgnoreStack")) { + if (target.getEffects().containsKey(stackType)) { + // remove any existing power that overrides + Effect ef = target.getEffects().get(stackType); + AbstractEffectJob effect = null; + if (ef != null) { + JobContainer jc = ef.getJobContainer(); + if (jc != null) + effect = (AbstractEffectJob) jc.getJob(); + } + ActionsBase overwrite = effect.getAction(); + + if (overwrite == null) { + Logger.error("NULL ACTION FOR POWER " + effect.getPowerToken()); + continue; + } + + if (ab.getStackOrder() < overwrite.getStackOrder()) + continue; // not high enough to overwrite + else if (ab.getStackOrder() > overwrite.getStackOrder()) { + effect.setNoOverwrite(true); + removeEffect(target, overwrite, true, false); + } else if (ab.getStackOrder() == overwrite.getStackOrder()) + if (ab.greaterThanEqual() + && (trains >= effect.getTrains())) { + effect.setNoOverwrite(true); + removeEffect(target, overwrite, true, false); + } else if (ab.always()) + removeEffect(target, overwrite, true, false); + else if (ab.greaterThan() + && (trains > effect.getTrains())) { + effect.setNoOverwrite(true); + removeEffect(target, overwrite, true, false); + } else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken()) + removeEffect(target, overwrite, true, false); + + else + continue; // trains not high enough to overwrite + + } + + // } + + + runPowerAction(playerCharacter, target, targetLoc, ab, trains, pb); + if (!miss && !msgCasted) { + PerformActionMsg castMsg = new PerformActionMsg(msg); + castMsg.setNumTrains(9999); + castMsg.setUnknown04(2); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + msgCasted = true; + } + } + } + + if (!msgCasted) { + PerformActionMsg castMsg = new PerformActionMsg(msg); + castMsg.setNumTrains(9999); + castMsg.setUnknown04(2); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + msgCasted = true; + } + + //Handle chant + if (pb != null && pb.isChant()) + for (ActionsBase ab : pb.getActions()) { + AbstractPowerAction pa = ab.getPowerAction(); + if (pa != null) + if (pb.getToken() != 428950414 && pb.getToken() != 428884878) + pa.handleChant(playerCharacter, playerCharacter, targetLoc, trains, ab, pb); + else if (PowersManager.getTarget(msg) != null && PowersManager.getTarget(msg).isAlive()) + pa.handleChant(playerCharacter, PowersManager.getTarget(msg), targetLoc, trains, ab, pb); + else + pa.handleChant(playerCharacter, null, targetLoc, trains, ab, pb); + } + + + //DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + + } + + public static void finishUseMobPower(PerformActionMsg msg, Mob caster, int casterLiveCounter, int targetLiveCounter) { + + if (caster == null || msg == null) + return; + + if (!caster.isAlive() || caster.getLiveCounter() != casterLiveCounter) + return; + + // set player is not casting for regens + caster.setIsCasting(false); + + + PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); + // clear power. + caster.setLastMobPowerToken(0); + + if (pb == null) { + Logger.error( + "finishUsePower(): Power '" + msg.getPowerUsedID() + + "' was not found on powersBaseByToken map."); + return; + } + + int trains = msg.getNumTrains(); + + // update used power timer + // verify player is not stunned or power type is blocked + PlayerBonuses bonus = caster.getBonuses(); + if (bonus != null) { + if (bonus.getBool(ModType.Stunned, SourceType.None)) + return; + SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); + if (bonus.getBool(ModType.BlockedPowerType, sourceType)) + return; + } + + msg.setNumTrains(9999); + msg.setUnknown04(2); + DispatchMessage.sendToAllInRange(caster, msg); + + // get target loc + Vector3fImmutable targetLoc = msg.getTargetLoc(); + if (targetLoc.x == 0f || targetLoc.z == 0f) { + AbstractWorldObject tar = getTarget(msg); + if (tar != null) + targetLoc = tar.getLoc(); + } + + // get list of targets + HashSet allTargets = getAllTargets( + getTarget(msg), msg.getTargetLoc(), caster, pb); + for (AbstractWorldObject target : allTargets) { + + if (target == null) + continue; + + + //make sure target hasn't respawned since we began casting + //skip this if liveCounter = -1 (from aoe) + if (targetLiveCounter != -1) + if (AbstractWorldObject.IsAbstractCharacter(target)) + if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter()) + continue; + + //make sure mob is awake to respond. + //if (target instanceof AbstractIntelligenceAgent) + //((AbstractIntelligenceAgent)target).enableIntelligence(); + // If Hit roll required, test hit + if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(caster, target, pb, msg)) + //aggro mob even on a miss + continue; + + // update target of used power timer + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) + ((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); + + // finally Apply actions + for (ActionsBase ab : pb.getActions()) { + // get numTrains for power, skip action if invalid + + if (trains < ab.getMinTrains() || trains > ab.getMaxTrains()) + continue; + // If something blocks the action, then stop + + if (ab.blocked(target, pb, trains)) + continue; + // TODO handle overwrite stack order here + String stackType = ab.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; + // if (!stackType.equals("IgnoreStack")) { + if (target.getEffects().containsKey(stackType)) { + // remove any existing power that overrides + Effect ef = target.getEffects().get(stackType); + AbstractEffectJob effect = null; + if (ef != null) { + JobContainer jc = ef.getJobContainer(); + if (jc != null) + effect = (AbstractEffectJob) jc.getJob(); + } + ActionsBase overwrite = effect.getAction(); + + if (overwrite == null) { + Logger.error("NULL ACTION FOR EFFECT " + effect.getPowerToken()); + continue; + } + if (ab.getStackOrder() < overwrite.getStackOrder()) + continue; // not high enough to overwrite + else if (ab.getStackOrder() > overwrite.getStackOrder()) { + effect.setNoOverwrite(true); + removeEffect(target, overwrite, true, false); + } else if (ab.getStackOrder() == overwrite.getStackOrder()) + if (ab.greaterThanEqual() + && (trains >= effect.getTrains())) { + effect.setNoOverwrite(true); + removeEffect(target, overwrite, true, false); + } else if (ab.always()) + removeEffect(target, overwrite, true, false); + else if (ab.greaterThan() + && (trains > effect.getTrains())) { + effect.setNoOverwrite(true); + removeEffect(target, overwrite, true, false); + } else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken()) + removeEffect(target, overwrite, true, false); + + else + continue; // trains not high enough to overwrite + } + + // } + runPowerAction(caster, target, targetLoc, ab, trains, pb); + } + } + + //Handle chant + if (pb != null && pb.isChant()) + for (ActionsBase ab : pb.getActions()) { + AbstractPowerAction pa = ab.getPowerAction(); + if (pa != null) + pa.handleChant(caster, caster, targetLoc, trains, ab, pb); + } + + // TODO echo power use to everyone else + msg.setNumTrains(9999); + msg.setUnknown04(2); + DispatchMessage.sendToAllInRange(caster, msg); + + } + + // *** Refactor : Wtf is this mess? + + private static boolean validMonsterType(AbstractWorldObject target, PowersBase pb) { + + if (pb == null || target == null) + return false; + + String mtp = pb.getMonsterTypePrereq(); + + if (mtp.length() == 0) + return true; + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + PlayerCharacter pc = (PlayerCharacter) target; + int raceID = 0; + + if (pc.getRace() != null) + raceID = pc.getRace().getRaceRuneID(); + + switch (mtp) { + case "Shade": + return raceID == 2015 || raceID == 2016; + case "Elf": + return raceID == 2008 || raceID == 2009; + case "Dwarf": + return raceID == 2006; + case "Aracoix": + return raceID == 2002 || raceID == 2003; + case "Irekei": + return raceID == 2013 || raceID == 2014; + case "Vampire": + return raceID == 2028 || raceID == 2029; + } + } else if (target.getObjectType().equals(GameObjectType.Mob)) { + Mob mob = (Mob) target; + + if (pb.targetMob() && !(mob.agentType.equals(AIAgentType.MOBILE)) && !mob.isSiege()) + return false; + else if (pb.targetPet() && !mob.isPet() && !mob.isSiege()) + return false; + + switch (mtp) { + case "Animal": + if ((mob.getObjectTypeMask() & MBServerStatics.MASK_BEAST) == 0) + return false; + break; + case "NPC": + if ((mob.getObjectTypeMask() & MBServerStatics.MASK_HUMANOID) == 0) + return false; + break; + case "Rat": + if ((mob.getObjectTypeMask() & MBServerStatics.MASK_RAT) == 0) + return false; + break; + case "Siege": + if (!mob.isSiege()) + return false; + break; + case "Undead": + if ((mob.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) == 0) + return false; + break; + } + return true; + } else + return target.getObjectType().equals(GameObjectType.Building) && mtp.equals("Siege"); + return false; + } + + public static void summon(SendSummonsRequestMsg msg, ClientConnection origin) { + PlayerCharacter pc = SessionManager.getPlayerCharacter( + origin); + if (pc == null) + return; + + PlayerCharacter target = SessionManager + .getPlayerCharacterByLowerCaseName(msg.getTargetName()); + if (target == null || target.equals(pc) || target.isCombat()) { + + if (target == null) // Player not found. Send not found message + ChatManager.chatInfoError(pc, + "Cannot find that player to summon."); + else if (target.isCombat()) + ChatManager.chatInfoError(pc, + "Cannot summon player in combat."); + // else trying to summon self, just fail + + // recycle summon + sendRecyclePower(msg.getPowerToken(), origin); + + // TODO: client already subtracted 200 mana.. need to correct it + // end cast + PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(), + msg.getTrains(), msg.getSourceType(), msg.getSourceID(), 0, + 0, 0f, 0f, 0f, 1, 0); + sendPowerMsg(pc, 2, pam); + + return; + } + + PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(), msg + .getTrains(), msg.getSourceType(), msg.getSourceID(), target + .getObjectType().ordinal(), target.getObjectUUID(), 0f, 0f, 0f, 1, 0); + + // Client removes 200 mana on summon use.. so don't send message to self + target.addSummoner(pc.getObjectUUID(), System.currentTimeMillis() + MBServerStatics.FOURTYFIVE_SECONDS); + usePower(pam, origin, false); + } + + public static void recvSummon(RecvSummonsRequestMsg msg, ClientConnection origin) { + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + if (pc == null) + return; + + PlayerCharacter source = PlayerCharacter.getFromCache(msg.getSourceID()); + if (source == null) + return; + + long tooLate = pc.getSummoner(source.getObjectUUID()); + if (tooLate < System.currentTimeMillis()) { + ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons."); + pc.removeSummoner(source.getObjectUUID()); + return; + } + + if (pc.getBonuses() != null && pc.getBonuses().getBool(ModType.BlockedPowerType, SourceType.SUMMON)) { + ErrorPopupMsg.sendErrorMsg(pc, "You have been blocked from receiving summons!"); + ErrorPopupMsg.sendErrorMsg(source, "Target is blocked from receiving summons!"); + pc.removeSummoner(source.getObjectUUID()); + return; + } + pc.removeSummoner(source.getObjectUUID()); + + // Handle Accepting or Denying a summons. + // set timer based on summon type. + boolean wentThrough = false; + if (msg.accepted()) + // summons accepted, let's move the player if within time + if (source.isAlive()) { + + // //make sure summons handled in time + ConcurrentHashMap timers = source.getTimers(); + // if (timers == null || !timers.containsKey("SummonSend")) { + // ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons."); + // return; + // } + + // // clear last summons accept timer + // timers.get("SummonSend").cancelJob(); + //timers.remove("SummonSend"); + // cancel any other summons waiting + timers = pc.getTimers(); + if (timers != null && timers.containsKey("Summon")) + timers.get("Summon").cancelJob(); + + // get time to wait before summons goes through + BaseClass base = source.getBaseClass(); + PromotionClass promo = source.getPromotionClass(); + int duration; + + + //determine if in combat with another player + + + //comment out this block to disable combat timer + // if (lastAttacked < 60000) { + // if (pc.inSafeZone()) //player in safe zone, no need for combat timer + // combat = false; + // else if (source.inSafeZone()) //summoner in safe zone, apply combat timer + // combat = true; + // else if ((source.getLoc().distance2D(pc.getLoc())) > 6144f) + // combat = true; //more than 1.5x width of zone, not tactical summons + // } + + if (promo != null && promo.getObjectUUID() == 2519) + duration = 10000; // Priest summons, 10 seconds + else if (base != null && base.getObjectUUID() == 2501) + duration = 15000; // Healer Summons, 15 seconds + else + duration = 45000; // Belgosh Summons, 45 seconds + + + // Teleport to summoners location + FinishSummonsJob fsj = new FinishSummonsJob(source, pc); + JobContainer jc = JobScheduler.getInstance().scheduleJob(fsj, + duration); + if (timers != null) + timers.put("Summon", jc); + wentThrough = true; + } + + // Summons failed + if (!wentThrough) + // summons refused. Let's be nice and reset recycle timer + if (source != null) { + + // Send summons refused Message + ErrorPopupMsg.sendErrorPopup(source, 29); + + // recycle summons power + //finishRecycleTime(428523680, source, true); + } + } + + public static void trackWindow(TrackWindowMsg msg, ClientConnection origin) { + + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter( + origin); + + if (playerCharacter == null) + return; + + if (MBServerStatics.POWERS_DEBUG) { + ChatManager.chatSayInfo( + playerCharacter, + "Using Power: " + Integer.toHexString(msg.getPowerToken()) + + " (" + msg.getPowerToken() + ')'); + Logger.info("Using Power: " + + Integer.toHexString(msg.getPowerToken()) + " (" + + msg.getPowerToken() + ')'); + } + + // get track power used + PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerToken()); + + if (pb == null || !pb.isTrack()) + return; + + //check track threshold timer to prevent spam + long currentTime = System.currentTimeMillis(); + long timestamp = playerCharacter.getTimeStamp("trackWindow"); + long dif = currentTime - timestamp; + if (dif < MBServerStatics.TRACK_WINDOW_THRESHOLD) + return; + playerCharacter.setTimeStamp("trackWindow", currentTime); + + ArrayList ablist = pb.getActions(); + if (ablist == null) + return; + + TrackPowerAction tpa = null; + for (ActionsBase ab : ablist) { + AbstractPowerAction apa = ab.getPowerAction(); + if (apa != null && apa instanceof TrackPowerAction) + tpa = (TrackPowerAction) apa; + } + if (tpa == null) + return; + + // Check powers for normal users + if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerToken())) + if (!playerCharacter.isCSR()) + if (!MBServerStatics.POWERS_DEBUG) { + // ChatManager.chatSayInfo(pc, "You may not cast that spell!"); + // this.logEXPLOIT("usePowerA(): Cheat attempted? '" + msg.getPowerToken() + "' was not associated with " + pc.getName()); + return; + } + + // Get search mask for track + int mask = 0; + if (pb.targetPlayer()) + if (tpa.trackVampire()) // track vampires + mask = MBServerStatics.MASK_PLAYER | MBServerStatics.MASK_UNDEAD; + else + // track all players + mask = MBServerStatics.MASK_PLAYER; + else if (pb.targetCorpse()) // track corpses + mask = MBServerStatics.MASK_CORPSE; + else if (tpa.trackNPC()) // Track NPCs + mask = MBServerStatics.MASK_NPC; + else if (tpa.trackUndead()) // Track Undead + mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_UNDEAD; + else + // Track All + mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_NPC; + + // Find characters in range + HashSet allTargets; + allTargets = WorldGrid.getObjectsInRangeContains(playerCharacter.getLoc(), + pb.getRange(), mask); + + //remove anyone who can't be tracked + Iterator it = allTargets.iterator(); + while (it.hasNext()) { + AbstractWorldObject awo = it.next(); + if (awo == null) + continue; + else if (!awo.isAlive()) + it.remove(); + else if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { + PlayerBonuses bonus = ((PlayerCharacter) awo).getBonuses(); + if (bonus != null && bonus.getBool(ModType.CannotTrack, SourceType.None)) + it.remove(); + } + } + + // get max charcters for window + int maxTargets = 20; + PromotionClass promo = playerCharacter.getPromotionClass(); + if (promo != null) { + int tableID = promo.getObjectUUID(); + if (tableID == 2512 || tableID == 2514 || tableID == 2515) + maxTargets = 40; + } + + // create list of characters + HashSet trackChars = RangeBasedAwo.getTrackList( + allTargets, playerCharacter, maxTargets); + + TrackWindowMsg trackWindowMsg = new TrackWindowMsg(msg); + + // send track window + trackWindowMsg.setSource(playerCharacter); + trackWindowMsg.setCharacters(trackChars); + + Dispatch dispatch = Dispatch.borrow(playerCharacter, trackWindowMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + private static void sendRecyclePower(int token, ClientConnection origin) { + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token); + + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + } + + public static boolean verifyInvalidRange(AbstractCharacter ac, + AbstractWorldObject target, float range) { + Vector3fImmutable sl = ac.getLoc(); + Vector3fImmutable tl = target.getLoc(); + if (target.getObjectType().equals(GameObjectType.Item)) { + + Item item = (Item) target; + AbstractGameObject owner = item.getOwner(); + + if (owner == null || owner.getObjectType().equals(GameObjectType.Account)) + return true; + + if (owner.getObjectType().equals(GameObjectType.PlayerCharacter) || owner.getObjectType().equals(GameObjectType.Mob)) { + AbstractCharacter acOwner = (AbstractCharacter) owner; + CharacterItemManager itemMan = acOwner.getCharItemManager(); + if (itemMan == null) + return true; + if (itemMan.inventoryContains(item)) { + tl = acOwner.getLoc(); + return !(sl.distanceSquared(tl) <= sqr(range)); + } + return true; + } + return true; + } + + range += (calcHitBox(ac) + calcHitBox(target)); + + + float distanceToTarget = sl.distanceSquared(tl);//distance to center of target + + return distanceToTarget > range * range; + + } + + public static float calcHitBox(AbstractWorldObject ac) { + //TODO Figure out how Str Affects HitBox + float hitBox = 1; + switch (ac.getObjectType()) { + case PlayerCharacter: + PlayerCharacter pc = (PlayerCharacter) ac; + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) + Logger.info("Hit box radius for " + pc.getFirstName() + " is " + ((int) pc.statStrBase / 200f)); + hitBox = 2f + (int) ((PlayerCharacter) ac).statStrBase / 50f; + break; + + case Mob: + Mob mob = (Mob) ac; + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) + Logger.info("Hit box radius for " + mob.getFirstName() + + " is " + ((Mob) ac).getMobBase().getHitBoxRadius()); + + hitBox = ((Mob) ac).getMobBase().getHitBoxRadius(); + break; + case Building: + Building building = (Building) ac; + if (building.getBlueprint() == null) + return 32; + hitBox = Math.max(building.getBlueprint().getBuildingGroup().getExtents().x, + building.getBlueprint().getBuildingGroup().getExtents().y); + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) + Logger.info("Hit box radius for " + building.getName() + " is " + hitBox); + break; + + } + return hitBox; + } + + // Apply a power based on it's IDString + public static void applyPower(AbstractCharacter ac, AbstractWorldObject target, + Vector3fImmutable targetLoc, String ID, int trains, boolean fromItem) { + if (ac == null || target == null || !ac.isAlive()) + return; + PowersBase pb = powersBaseByIDString.get(ID); + if (pb == null) { + Logger.error( + "applyPower(): Got NULL on powersBaseByIDString table lookup for: " + + ID); + return; + } + applyPowerA(ac, target, targetLoc, pb, trains, fromItem); + } + + // Apply a power based on it's Token + public static void applyPower(AbstractCharacter ac, AbstractWorldObject target, + Vector3fImmutable targetLoc, int token, int trains, boolean fromItem) { + if (ac == null || target == null) + return; + + //Don't apply power if ac is dead, unless death shroud or safe mode + if (!ac.isAlive()) + if (!(token == -1661758934 || token == 1672601862)) + return; + + PowersBase pb = powersBaseByToken.get(token); + if (pb == null) { + Logger.error( + "applyPower(): Got NULL on powersBaseByToken table lookup for: " + + token); + return; + } + applyPowerA(ac, target, targetLoc, pb, trains, fromItem); + } + + private static void applyPowerA(AbstractCharacter ac, AbstractWorldObject target, + Vector3fImmutable targetLoc, PowersBase pb, int trains, + boolean fromItem) { + int time = pb.getCastTime(trains); + if (!fromItem) + finishApplyPowerA(ac, target, targetLoc, pb, trains, false); + else if (time == 0) + finishApplyPower(ac, target, targetLoc, pb, trains, ac.getLiveCounter()); + else { + + ac.setItemCasting(true); + int tarType = (target == null) ? 0 : target.getObjectType().ordinal(); + int tarID = (target == null) ? 0 : target.getObjectUUID(); + + // start the action animation + PerformActionMsg msg = new PerformActionMsg(pb.getToken(), + trains, ac.getObjectType().ordinal(), ac.getObjectUUID(), tarType, tarID, 0, + 0, 0, 1, 0); + DispatchMessage.sendToAllInRange(target, msg); + + + ConcurrentHashMap timers = ac.getTimers(); + + if (timers.containsKey(Integer.toString(pb.getToken()))) { + JobContainer jc = timers.get(Integer.toString(pb.getToken())); + if (jc != null) + jc.cancelJob(); + } + + // // clear any other items being used + // JobContainer jc = ac.getLastItem(); + // if (jc != null) { + // jc.cancelJob(); + // ac.clearLastItem(); + // } + // run timer job to end cast + UseItemJob uij = new UseItemJob(ac, target, pb, trains, ac.getLiveCounter()); + JobContainer jc = js.scheduleJob(uij, time); + + // make lastItem + timers.put(Integer.toString(pb.getToken()), jc); + } + } + + public static void finishApplyPower(AbstractCharacter ac, + AbstractWorldObject target, Vector3fImmutable targetLoc, + PowersBase pb, int trains, int liveCounter) { + + if (ac != null) + ac.setItemCasting(false); + if (ac == null || target == null || pb == null) + return; + + ac.clearTimer(Integer.toString(pb.getToken())); + if (liveCounter == ac.getLiveCounter()) + finishApplyPowerA(ac, target, targetLoc, pb, trains, false); + } + + public static void finishApplyPowerA(AbstractCharacter ac, + AbstractWorldObject target, Vector3fImmutable targetLoc, + PowersBase pb, int trains, boolean fromChant) { + // finally Apply actions + ArrayList actions = pb.getActions(); + for (ActionsBase ab : actions) { + // get numTrains for power, skip action if invalid + if (trains < ab.getMinTrains() || trains > ab.getMaxTrains()) + continue; + // If something blocks the action, then stop + if (ab.blocked(target, pb, trains)) + // sendPowerMsg(pc, 5, msg); + continue; + // TODO handle overwrite stack order here + String stackType = ab.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; + if (target.getEffects().containsKey(stackType)) { + // remove any existing power that overrides + Effect ef = target.getEffects().get(stackType); + AbstractEffectJob effect = null; + if (ef != null) { + JobContainer jc = ef.getJobContainer(); + if (jc != null) + effect = (AbstractEffectJob) jc.getJob(); + } + ActionsBase overwrite = effect.getAction(); + PowersBase pbOverwrite = effect.getPower(); + if (pbOverwrite != null && pbOverwrite.equals(pb) + && (trains >= effect.getTrains())) + removeEffect(target, overwrite, true, fromChant); + else if (ab.getStackOrder() < overwrite.getStackOrder()) + continue; // not high enough to overwrite + else if (ab.getStackOrder() > overwrite.getStackOrder()) + removeEffect(target, overwrite, true, false); + else if (ab.getStackOrder() == overwrite.getStackOrder()) + if (ab.greaterThanEqual() + && (trains >= effect.getTrains())) + removeEffect(target, overwrite, true, false); + else if (ab.always()) + removeEffect(target, overwrite, true, false); + else if (ab.greaterThan() + && (trains > effect.getTrains())) + removeEffect(target, overwrite, true, false); + else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken()) + removeEffect(target, overwrite, true, false); + else + continue; // trains not high enough to overwrite + } + if (fromChant) + targetLoc = Vector3fImmutable.ZERO; + runPowerAction(ac, target, targetLoc, ab, trains, pb); + } + + //Handle chant + if (pb != null && pb.isChant()) + for (ActionsBase ab : pb.getActions()) { + AbstractPowerAction pa = ab.getPowerAction(); + if (pa != null) + pa.handleChant(ac, target, targetLoc, trains, ab, pb); + } + + // for chants, only send the animation if the character is not is not moving or casting + boolean doAnimation = true; + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) target; + if (pb != null && pb.isChant() && (pc.isMoving() || pc.isCasting())) + doAnimation = false; + } + + if (pb.getToken() == 428950414) + doAnimation = true; + + if (doAnimation) { + PerformActionMsg msg = new PerformActionMsg(pb.getToken(), 9999, ac + .getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(), + target.getObjectUUID(), 0, 0, 0, 2, 0); + + DispatchMessage.sendToAllInRange(ac, msg); + + } + } + + public static void runPowerAction(AbstractCharacter source, + AbstractWorldObject awo, Vector3fImmutable targetLoc, + ActionsBase ab, int trains, PowersBase pb) { + AbstractPowerAction pa = ab.getPowerAction(); + if (pa == null) { + Logger.error( + "runPowerAction(): PowerAction not found of IDString: " + + ab.getEffectID()); + return; + } + pa.startAction(source, awo, targetLoc, trains, ab, pb); + } + + public static void runPowerAction(AbstractCharacter source, + AbstractWorldObject awo, Vector3fImmutable targetLoc, + ActionsBase ab, int trains, PowersBase pb, int duration) { + AbstractPowerAction pa = ab.getPowerAction(); + if (pa == null) { + Logger.error( + "runPowerAction(): PowerAction not found of IDString: " + + ab.getEffectID()); + return; + } + pa.startAction(source, awo, targetLoc, trains, ab, pb, duration); + } + + public static HashSet getAllTargets( + AbstractWorldObject target, Vector3fImmutable tl, + PlayerCharacter pc, PowersBase pb) { + HashSet allTargets; + if (pb.isAOE()) { + Vector3fImmutable targetLoc = null; + if (pb.usePointBlank()) { + targetLoc = pc.getLoc(); + } else { + if (target != null) { + targetLoc = target.getLoc(); + } else { + targetLoc = tl; + try { + targetLoc = targetLoc.setY(HeightMap.getWorldHeight(targetLoc)); //on ground + } catch (Exception e) { + Logger.error(e); + targetLoc = tl; + } + + } + } + + if (targetLoc.x == 0f || targetLoc.z == 0f) + return new HashSet<>(); // invalid loc, + // return + // nothing + + //first find targets in range quickly with QTree + if (pb.targetPlayer() && pb.targetMob()) + // Player and mobs + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE); + else if (pb.targetPlayer()) + // Player only + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER); + else if (pb.targetMob()) + // Mob only + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB + | MBServerStatics.MASK_PET); + else if (pb.targetPet()) + //Pet only + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_PET); + else if (pb.targetNecroPet()) + allTargets = WorldGrid.getObjectsInRangePartialNecroPets( + targetLoc, pb.getRadius()); + else + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), 0); + + // cleanup self, group and nation targets if needed + Iterator awolist = allTargets.iterator(); + while (awolist.hasNext()) { + AbstractWorldObject awo = awolist.next(); + if (awo == null) { + awolist.remove(); // won't hit a null + continue; + } + + //see if targets are within 3D range of each other + Vector3fImmutable tloc = awo.getLoc(); + + if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) { + awolist.remove(); // too far away + continue; + } + + if (pb.isCasterFriendly() && pc.equals(awo)) { + awolist.remove(); // won't hit self + continue; + } + + if (!awo.isAlive()) { + awolist.remove(); // too far away + continue; + } + + if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + PlayerCharacter pcc = (PlayerCharacter) awo; + + if (pb.isGroupFriendly() && GroupManager.getGroup(pc) != null && GroupManager.getGroup(pcc) != null) + if (GroupManager.getGroup(pc).equals(GroupManager.getGroup(pcc))) { + awolist.remove(); // Won't hit group members + continue; + } + if (pb.isNationFriendly() && pc.getGuild() != null && + pc.getGuild().getNation() != null && pcc.getGuild() != null && + pc.getGuild().getNation() != null) + if (pc.getGuild().getNation().equals(pcc.getGuild().getNation())) { + awolist.remove(); // Won't hit nation members + continue; + } + + // Remove players for non-friendly spells in safe zone + if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) { + awolist.remove(); + continue; + } + } + } + // Trim list down to max size closest targets, limited by max + // Player/Mob amounts + allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb + .getMaxNumPlayerTargets(), pb.getMaxNumMobTargets()); + } else if (pb.targetGroup()) { + + if (GroupManager.getGroup(pc) != null) { + allTargets = WorldGrid.getObjectsInRangePartial(pc + .getLoc(), pb.getRange(), MBServerStatics.MASK_PLAYER); + Iterator awolist = allTargets.iterator(); + while (awolist.hasNext()) { + + AbstractWorldObject awo = awolist.next(); + + if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) { + awolist.remove(); // remove non players if there are any + continue; + } + PlayerCharacter pcc = (PlayerCharacter) awo; + + if (GroupManager.getGroup(pcc) == null) + awolist.remove(); // remove players not in a group + else if (!GroupManager.getGroup(pcc).equals(GroupManager.getGroup(pc))) + awolist.remove(); // remove if not same group + + } + } else { + allTargets = new HashSet<>(); + allTargets.add(pc); // no group, use only self + } + } else { + allTargets = new HashSet<>(); + if (pb.targetSelf()) + allTargets.add(pc); + else if (pb.targetFromLastTarget()) + allTargets.add(target); + else if (pb.targetFromNearbyMobs()) + allTargets.add(target); // need better way to do this later + else + // targetByName + allTargets.add(target); // need to get name later + // can't target self if caster friendly + if (pb.isCasterFriendly() && allTargets.contains(pc)) + allTargets.remove(0); + } + + Iterator awolist = allTargets.iterator(); + while (awolist.hasNext()) { + AbstractWorldObject awo = awolist.next(); + + //See if target is valid type + if (!validMonsterType(awo, pb)) { + awolist.remove(); + continue; + } + + if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + // Remove players who are in safe mode + PlayerCharacter pcc = (PlayerCharacter) awo; + PlayerBonuses bonuses = pcc.getBonuses(); + + if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.None)) { + awolist.remove(); + continue; + } + + //remove if power is harmful and caster or target is in safe zone + if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) { + awolist.remove(); + continue; + } + } + } + + // verify target has proper effects applied to receive power + if (pb.getTargetEffectPrereqs().size() > 0) { + Iterator it = allTargets.iterator(); + while (it.hasNext()) { + boolean passed = false; + AbstractWorldObject awo = it.next(); + if (awo.getEffects() != null) { + for (PowerPrereq pp : pb.getTargetEffectPrereqs()) { + EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect()); + if (awo.containsEffect(eb.getToken())) { + passed = true; + break; + } + } + if (!passed) + it.remove(); + } else + it.remove(); //awo is missing it's effects list + } + } + return allTargets; + } + + public static HashSet getAllTargets( + AbstractWorldObject target, Vector3fImmutable tl, + AbstractCharacter caster, PowersBase pb) { + HashSet allTargets; + if (pb.isAOE()) { + Vector3fImmutable targetLoc = tl; + if (pb.usePointBlank()) { + targetLoc = caster.getLoc(); + } else { + if (target != null) { + targetLoc = target.getLoc(); + } else { + targetLoc = tl; + try { + targetLoc = targetLoc.setY(HeightMap.getWorldHeight(targetLoc)); //on ground + } catch (Exception e) { + Logger.error(e); + } + + } + } + + if (targetLoc.x == 0f || targetLoc.z == 0f) + return new HashSet<>(); // invalid loc, + + //first find targets in range quickly with QTree + if (pb.targetPlayer() && pb.targetMob()) + // Player and mobs + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE); + else if (pb.targetPlayer()) + // Player only + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER); + else if (pb.targetMob()) + // Mob only + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB + | MBServerStatics.MASK_PET); + else if (pb.targetPet()) + //Pet only + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), MBServerStatics.MASK_PET); + else if (pb.targetNecroPet()) + allTargets = WorldGrid.getObjectsInRangePartialNecroPets( + targetLoc, pb.getRadius()); + else + allTargets = WorldGrid.getObjectsInRangePartial( + targetLoc, pb.getRadius(), 0); + + // cleanup self, group and nation targets if needed + Iterator awolist = allTargets.iterator(); + while (awolist.hasNext()) { + AbstractWorldObject awo = awolist.next(); + if (awo == null) { + awolist.remove(); // won't hit a null + continue; + } + + //see if targets are within 3D range of each other + Vector3fImmutable tloc = awo.getLoc(); + + if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) { + awolist.remove(); // too far away + continue; + } + + if (pb.isCasterFriendly() && caster.equals(awo)) { + awolist.remove(); // won't hit self + continue; + } + + if (awo.getObjectType() == GameObjectType.Mob) { + awolist.remove(); // Won't hit other mobs. + continue; + } + } + // Trim list down to max size closest targets, limited by max + // Player/Mob amounts + allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb + .getMaxNumPlayerTargets(), pb.getMaxNumMobTargets()); + } else if (pb.targetGroup()) { + allTargets = new HashSet<>(); + allTargets.add(caster); // no group, use only self + } else { + allTargets = new HashSet<>(); + if (pb.targetSelf()) + allTargets.add(caster); + else if (pb.targetFromLastTarget()) + allTargets.add(target); + else if (pb.targetFromNearbyMobs()) + allTargets.add(target); // need better way to do this later + else + // targetByName + allTargets.add(target); // need to get name later + // can't target self if caster friendly + if (pb.isCasterFriendly() && allTargets.contains(caster)) + allTargets.remove(caster); + } + + Iterator awolist = allTargets.iterator(); + while (awolist.hasNext()) { + AbstractWorldObject awo = awolist.next(); + + //See if target is valid type + if (!validMonsterType(awo, pb)) { + awolist.remove(); + continue; + } + + if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { + // Remove players who are in safe mode + PlayerCharacter pcc = (PlayerCharacter) awo; + PlayerBonuses bonuses = pcc.getBonuses(); + if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.None)) { + awolist.remove(); + continue; + } + } + } + + // verify target has proper effects applied to receive power + if (pb.getTargetEffectPrereqs().size() > 0) { + Iterator it = allTargets.iterator(); + while (it.hasNext()) { + boolean passed = false; + AbstractWorldObject awo = it.next(); + if (awo.getEffects() != null) { + for (PowerPrereq pp : pb.getTargetEffectPrereqs()) { + EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect()); + if (awo.containsEffect(eb.getToken())) { + passed = true; + break; + } + } + if (!passed) + it.remove(); + } else + it.remove(); //awo is missing it's effects list + } + } + return allTargets; + } + + // removes an effect before time is finished + public static void removeEffect(AbstractWorldObject awo, ActionsBase toRemove, + boolean overwrite, boolean fromChant) { + if (toRemove == null) + return; + + String stackType = toRemove.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer + .toString(toRemove.getUUID()) : stackType; + if (fromChant) { + Effect eff = awo.getEffects().get(stackType); + if (eff != null) + eff.cancelJob(true); + } else + awo.cancelEffect(stackType, overwrite); + } + + // removes an effect when timer finishes + public static void finishEffectTime(AbstractWorldObject source, + AbstractWorldObject awo, ActionsBase toRemove, int trains) { + if (awo == null || toRemove == null) + return; + + // remove effect from player + String stackType = toRemove.getStackType(); + if (stackType.equals("IgnoreStack")) + stackType = Integer.toString(toRemove.getUUID()); + awo.endEffect(stackType); + } + + // removes an effect when timer is canceled + public static void cancelEffectTime(AbstractWorldObject source, + AbstractWorldObject awo, PowersBase pb, EffectsBase eb, + ActionsBase toRemove, int trains, AbstractEffectJob efj) { + if (awo == null || pb == null || eb == null || toRemove == null) + return; + eb.endEffect(source, awo, trains, pb, efj); + } + + // called when cooldown ends letting player cast next spell + public static void finishCooldownTime(PerformActionMsg msg, PlayerCharacter pc) { + // clear spell so player can cast again + // if (pc != null) + // pc.clearLastPower(); + } + + // called when recycle time ends letting player cast spell again + public static void finishRecycleTime(PerformActionMsg msg, PlayerCharacter pc, + boolean canceled) { + finishRecycleTime(msg.getPowerUsedID(), pc, canceled); + } + + public static void finishRecycleTime(int token, PlayerCharacter pc, + boolean canceled) { + if (pc == null) + return; + + ConcurrentHashMap recycleTimers = pc + .getRecycleTimers(); + // clear recycle time + if (recycleTimers != null) + if (recycleTimers.containsKey(token)) { + if (canceled) { + JobContainer jc = recycleTimers.get(token); + if (jc != null) + jc.cancelJob(); + } + recycleTimers.remove(token); + } + + // send recycle message to unlock power + + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token); + Dispatch dispatch = Dispatch.borrow(pc, recyclePowerMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + } + + // Called when a fail condition is met by player + // such as moving, taking damage, ect. + + public static void cancelUseLastPower(PlayerCharacter pc) { + + if (pc == null) + return; + + // set player is not casting for regens + if (pc.isCasting()) { + pc.update(); + } + pc.setIsCasting(false); + + UsePowerJob lastPower = null; + JobContainer jc = pc.getLastPower(); + + if (jc != null) + lastPower = ((UsePowerJob) jc.getJob()); + + if (lastPower == null) + return; + + // clear recycle timer + int token = lastPower.getToken(); + + if (pc.getRecycleTimers().contains(token)) + finishRecycleTime(token, pc, true); + + // pc.getRecycleTimers().remove(token); + // Cancel power + js.cancelScheduledJob(lastPower); + + // clear last power + pc.clearLastPower(); + + } + + private static AbstractWorldObject getTarget(PerformActionMsg msg) { + + int type = msg.getTargetType(); + int UUID = msg.getTargetID(); + + if (type == -1 || type == 0 || UUID == -1 || UUID == 0) + return null; + + return (AbstractWorldObject) DbManager.getObject(GameObjectType.values()[type], UUID); + } + + public static boolean testAttack(PlayerCharacter pc, AbstractWorldObject awo, + PowersBase pb, PerformActionMsg msg) { + // Get defense for target + float atr = CharacterSkill.getATR(pc, pb.getSkillName()); + float defense; + + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter tar = (AbstractCharacter) awo; + defense = tar.getDefenseRating(); + } else + defense = 0f; + // Get hit chance + + if (pc.getDebug(16)) { + String smsg = "ATR: " + atr + ", Defense: " + defense; + ChatManager.chatSystemInfo(pc, smsg); + } + + int chance; + + if (atr > defense || defense == 0) + chance = 94; + else { + float dif = atr / defense; + if (dif <= 0.8f) + chance = 4; + else + chance = ((int) (450 * (dif - 0.8f)) + 4); + } + + // calculate hit/miss + int roll = ThreadLocalRandom.current().nextInt(100); + + boolean disable = true; + if (roll < chance) { + // Hit, check if dodge kicked in + if (awo instanceof AbstractCharacter) { + AbstractCharacter tarAc = (AbstractCharacter) awo; + // Handle Dodge passive + if (testPassive(pc, tarAc, "Dodge")) { + // Dodge fired, send dodge message + PerformActionMsg dodgeMsg = new PerformActionMsg(msg); + dodgeMsg.setTargetType(awo.getObjectType().ordinal()); + dodgeMsg.setTargetID(awo.getObjectUUID()); + sendPowerMsg(pc, 4, dodgeMsg); + return true; + } + } + return false; + } else { + // Miss. Send miss message + PerformActionMsg missMsg = new PerformActionMsg(msg); + + missMsg.setTargetType(awo.getObjectType().ordinal()); + missMsg.setTargetID(awo.getObjectUUID()); + sendPowerMsg(pc, 3, missMsg); + return true; + } + } + + public static boolean testAttack(Mob caster, AbstractWorldObject awo, + PowersBase pb, PerformActionMsg msg) { + // Get defense for target + float atr = 2000; + float defense; + + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter tar = (AbstractCharacter) awo; + defense = tar.getDefenseRating(); + } else + defense = 0f; + // Get hit chance + + int chance; + + if (atr > defense || defense == 0) + chance = 94; + else { + float dif = atr / defense; + if (dif <= 0.8f) + chance = 4; + else + chance = ((int) (450 * (dif - 0.8f)) + 4); + } + + // calculate hit/miss + int roll = ThreadLocalRandom.current().nextInt(100); + + if (roll < chance) { + // Hit, check if dodge kicked in + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter tarAc = (AbstractCharacter) awo; + // Handle Dodge passive + return testPassive(caster, tarAc, "Dodge"); + } + return false; + } else + return true; + } + + public static void sendPowerMsg(PlayerCharacter playerCharacter, int type, PerformActionMsg msg) { + + if (playerCharacter == null) + return; + + msg.setUnknown05(type); + + switch (type) { + case 3: + case 4: + msg.setUnknown04(2); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + break; + default: + msg.setUnknown04(1); + Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + } + + public static void sendEffectMsg(PlayerCharacter pc, int type, ActionsBase ab, PowersBase pb) { + + if (pc == null) + return; + + try { + + EffectsBase eb = PowersManager.effectsBaseByIDString.get(ab.getEffectID()); + + if (eb == null) + return; + + ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 9, pb.getToken(), pb.getName()); + aem.setUnknown03(type); + DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + } + + public static void sendEffectMsg(PlayerCharacter pc, int type, EffectsBase eb) { + + if (pc == null) + return; + try { + + if (eb == null) + return; + ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 0, eb.getToken(), ""); + aem.setUnknown03(type); + aem.setUnknown05(1); + + DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + } + + public static void sendMobPowerMsg(Mob mob, int type, PerformActionMsg msg) { + + msg.setUnknown05(type); + switch (type) { + case 3: + case 4: + DispatchMessage.sendToAllInRange(mob, msg); + + } + } + + private static boolean testPassive(AbstractCharacter source, + AbstractCharacter target, String type) { + + float chance = target.getPassiveChance(type, source.getLevel(), false); + + if (chance == 0f) + return false; + + // max 75% chance of passive to fire + if (chance > 75f) + chance = 75f; + + int roll = ThreadLocalRandom.current().nextInt(100); + // Passive fired + // TODO send message + // Passive did not fire + return roll < chance; + } + + private static boolean validateTarget(AbstractWorldObject target, + PlayerCharacter pc, PowersBase pb) { + + //group target. uses pbaoe rules + if (pb.targetGroup()) + return true; + + // target is player + else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PLAYER) != 0) { + if (pb.targetPlayer()) + if (pb.isGroupOnly()) { //single target group only power + PlayerCharacter trg = (PlayerCharacter) target; + + if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null) + if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc).getObjectUUID()) + return true; // both in same group, good to go + return trg != null && pc.getObjectUUID() == trg.getObjectUUID(); + } else + return true; // can target player, good to go. + else if (target.getObjectUUID() == pc.getObjectUUID() && pb.targetSelf()) + return true; // can target self, good to go + else if (pb.targetCorpse()) { + //target is dead player + PlayerCharacter trg = (PlayerCharacter) target; + return !trg.isAlive(); + } else { + PlayerCharacter trg = (PlayerCharacter) target; + + if (pb.targetGroup()) + if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null) + if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc) + .getObjectUUID()) + return true; // both in same group, good to go + if (pb.targetGuildLeader()) + if (pc.getGuild() != null) + if (pc.getGuild().getGuildLeaderUUID() == trg.getObjectUUID()) + return true; // can hit guild leader, good to go + } + String outmsg = "Invalid Target"; + ChatManager.chatSystemInfo(pc, outmsg); + return false; // can't target player, stop here + } // target is mob + else if ((target.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0) + return pb.targetMob(); + + // target is pet + else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0) + return pb.targetPet(); + + // target is Building + else if ((target.getObjectTypeMask() & MBServerStatics.MASK_BUILDING) != 0) + return pb.targetBuilding(); + + else if (target.getObjectType().equals(GameObjectType.Item)) { + Item item = (Item) target; + if (pb.targetItem()) + return true; + // TODO add these checks later + else if (pb.targetArmor() && item.getItemBase().getType().equals(ItemType.ARMOR)) + return true; + else if (pb.targetJewelry() && item.getItemBase().getType().equals(ItemType.JEWELRY)) + return true; + else + return pb.targetWeapon() && item.getItemBase().getType().equals(ItemType.WEAPON); + } // How did we get here? all valid targets have been covered + else + return false; + } + + /* + * Cancel spell upon actions + */ + public static void cancelOnAttack(AbstractCharacter ac) { + ac.cancelTimer("Stuck"); + } + + public static void cancelOnAttackSwing(AbstractCharacter ac) { + } + + public static void cancelOnCast(AbstractCharacter ac) { + + } + + public static void cancelOnSpell(AbstractCharacter ac) { + + PowersBase power = getLastPower(ac); + + if (power != null && power.cancelOnCastSpell()) + cancelPower(ac, false); + ac.cancelLastChant(); + } + + public static void cancelOnEquipChange(AbstractCharacter ac) { + + } + + public static void cancelOnLogout(AbstractCharacter ac) { + + } + + public static void cancelOnMove(AbstractCharacter ac) { + + PowersBase power = getLastPower(ac); + + if (power != null && !power.canCastWhileMoving()) + cancelPower(ac, false); + + //cancel items + cancelItems(ac, true, false); + ac.cancelTimer("Stuck"); + } + + + public static void cancelOnNewCharm(AbstractCharacter ac) { + + } + + public static void cancelOnSit(AbstractCharacter ac) { + cancelPower(ac, false); // Always cancel casts on sit + } + + public static void cancelOnTakeDamage(AbstractCharacter ac) { + + PowersBase power = getLastPower(ac); + + if (power != null && power.cancelOnTakeDamage()) + cancelPower(ac, true); + cancelItems(ac, false, true); + ac.cancelTimer("Stuck"); + } + + public static void cancelOnTerritoryClaim(AbstractCharacter ac) { + + } + + public static void cancelOnUnEquip(AbstractCharacter ac) { + + } + + public static void cancelOnStun(AbstractCharacter ac) { + + } + + private static PowersBase getLastPower(AbstractCharacter ac) { + if (ac == null) + return null; + + JobContainer jc = ac.getLastPower(); + + if (jc == null) + return null; + + AbstractJob aj = jc.getJob(); + + if (aj == null) + return null; + + if (aj instanceof UsePowerJob) { + UsePowerJob upj = (UsePowerJob) aj; + return upj.getPowersBase(); + } + return null; + } + + private static PowersBase getLastItem(AbstractCharacter ac) { + + if (ac == null) + return null; + + JobContainer jc = ac.getLastItem(); + + if (jc == null) + return null; + + AbstractJob aj = jc.getJob(); + + if (aj == null) + return null; + + if (aj instanceof UseItemJob) { + UseItemJob uij = (UseItemJob) aj; + return uij.getPowersBase(); + } + return null; + } + + //cancels last casted power + private static void cancelPower(AbstractCharacter ac, boolean cancelCastAnimation) { + + if (ac == null) + return; + + JobContainer jc = ac.getLastPower(); + + if (jc == null) + return; + + AbstractJob aj = jc.getJob(); + + if (aj == null) + return; + + if (aj instanceof AbstractScheduleJob) + ((AbstractScheduleJob) aj).cancelJob(); + + ac.clearLastPower(); + + //clear cast animation for everyone in view range + if (aj instanceof UsePowerJob && cancelCastAnimation) { + + PerformActionMsg pam = ((UsePowerJob) aj).getMsg(); + + if (pam != null) { + pam.setNumTrains(9999); + pam.setUnknown04(2); + DispatchMessage.sendToAllInRange(ac, pam); + } + } + } + + public static PerformActionMsg createPowerMsg(PowersBase pb, int trains, AbstractCharacter source, AbstractCharacter target) { + return new PerformActionMsg(pb.getToken(), trains, source.getObjectType().ordinal(), source.getObjectUUID(), target.getObjectType().ordinal(), target.getObjectUUID(), target.getLoc().x, target.getLoc().y, target.getLoc().z, 0, 0); + + } + + //cancels any casts from using an item + + private static void cancelItems(AbstractCharacter ac, boolean cancelOnMove, boolean cancelOnTakeDamage) { + JobContainer jc; + AbstractJob aj; + ConcurrentHashMap timers; + UseItemJob uij; + PowersBase pb; + AbstractWorldObject target; + + if (ac == null) + return; + + timers = ac.getTimers(); + + if (timers == null) + return; + + for (String name : timers.keySet()) { + + jc = timers.get(name); + + if (jc == null) + continue; + + aj = jc.getJob(); + + if (aj != null && aj instanceof UseItemJob) { + uij = (UseItemJob) aj; + pb = uij.getPowersBase(); + + if (pb == null) + continue; + + if (!pb.canCastWhileMoving() && cancelOnMove) { + uij.cancelJob(); + timers.remove(name); + continue; + } + + if ((pb.cancelOnTakeDamage() == false) && + (cancelOnTakeDamage == false)) + continue; + + uij.cancelJob(); + timers.remove(name); + + //clear cast animation for everyone in view range + target = uij.getTarget(); + + if (target != null) { + PerformActionMsg pam = new PerformActionMsg(pb.getToken(), 9999, ac + .getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(), + target.getObjectUUID(), 0, 0, 0, 2, 0); + DispatchMessage.sendToAllInRange(ac, pam); + + } + } + } + } + +} + + + diff --git a/src/engine/gameManager/SessionManager.java b/src/engine/gameManager/SessionManager.java new file mode 100644 index 00000000..d7032bca --- /dev/null +++ b/src/engine/gameManager/SessionManager.java @@ -0,0 +1,286 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.net.client.ClientConnection; +import engine.objects.Account; +import engine.objects.Guild; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import engine.session.CSSession; +import engine.session.Session; +import engine.session.SessionID; +import engine.util.ByteUtils; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; + +public enum SessionManager { + + SESSIONMANAGER; + + // TODO add session activity timestamping & timeout monitors + + public static int _maxPopulation = 0; + private static ConcurrentHashMap sessionIDtoSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + private static ConcurrentHashMap pcToSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + private static ConcurrentHashMap accountToSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + private static ConcurrentHashMap connToSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + // 0 = login server + // 1 = gateway server + // 2 = all other servers + private static int crossServerBehavior = 2; + + public static Session getNewSession(SessionID sesID, Account a, ClientConnection c) { + Session ses = new Session(sesID, a, c); + + SessionManager.sessionIDtoSession.put(sesID, ses); + SessionManager.accountToSession.put(a, ses); + SessionManager.connToSession.put(c, ses); + + if (crossServerBehavior == 0) + if (!CSSession.addCrossServerSession(ByteUtils.byteArrayToSafeStringHex(c.getSecretKeyBytes()), a, c.getSocketChannel() + .socket().getInetAddress(), c.machineID)) + Logger.warn("Failed to create cross server session: " + a.getUname()); + + return ses; + } + + public static Session getNewSession(Account a, ClientConnection c) { + SessionID sesID = c.getSessionID(); + return SessionManager.getNewSession(sesID, a, c); + } + + + public static void cSessionCleanup(String key) { + if (!CSSession.deleteCrossServerSession(key)) + Logger.warn( + "Failed to remove cross server session for key: " + key); + } + + public static void remSession(Session s) { + + if (s == null) { + return; + } + + SessionManager.remSessionID(s); + SessionManager.remAccount(s); + SessionManager.remClientConnection(s); + SessionManager.remPlayerCharacter(s); + + //TODO LATER fix + s.setAccount(null); + s.setConn(null); + s.setPlayerCharacter(null); + s.setSessionID(null); + } + + /* + * Get Sessions + */ + public static Session getSession(SessionID id) { + return SessionManager.sessionIDtoSession.get(id); + } + + public static Session getSession(PlayerCharacter pc) { + return SessionManager.pcToSession.get(pc); + } + + public static Session getSession(Account a) { + return SessionManager.accountToSession.get(a); + } + + public static Session getSession(ClientConnection cc) { + return SessionManager.connToSession.get(cc); + } + + /* + * Get Connections + */ + public static ClientConnection getClientConnection(SessionID id) { + Session s = SessionManager.getSession(id); + return (s == null) ? null : s.getConn(); + } + + public static ClientConnection getClientConnection(PlayerCharacter pc) { + Session s = SessionManager.getSession(pc); + return (s == null) ? null : s.getConn(); + } + + public static ClientConnection getClientConnection(Account a) { + Session s = SessionManager.getSession(a); + return (s == null) ? null : s.getConn(); + } + + /* + * Get PlayerCharacter + */ + public static PlayerCharacter getPlayerCharacter(SessionID id) { + Session s = SessionManager.getSession(id); + return (s == null) ? null : s.getPlayerCharacter(); + } + + public static PlayerCharacter getPlayerCharacter(ClientConnection conn) { + Session s = SessionManager.getSession(conn); + return (s == null) ? null : s.getPlayerCharacter(); + } + + public static PlayerCharacter getPlayerCharacter(Account a) { + Session s = SessionManager.getSession(a); + return (s == null) ? null : s.getPlayerCharacter(); + } + + /* + * Get Account + */ + public static Account getAccount(SessionID id) { + Session s = SessionManager.getSession(id); + return (s == null) ? null : s.getAccount(); + } + + public static Account getAccount(ClientConnection conn) { + Session s = SessionManager.getSession(conn); + return (s == null) ? null : s.getAccount(); + } + + public static Account getAccount(PlayerCharacter pc) { + Session s = SessionManager.getSession(pc); + return (s == null) ? null : s.getAccount(); + } + + public static void setPlayerCharacter(Session s, PlayerCharacter pc) { + SessionManager.pcToSession.put(pc, s); + s.setPlayerCharacter(pc); + + // Update max player + SessionManager._maxPopulation = Math.max(_maxPopulation, SessionManager.pcToSession.size()); + + } + + public static void remPlayerCharacter(Session s) { + if (s.getPlayerCharacter() != null) { + SessionManager.pcToSession.remove(s.getPlayerCharacter()); + s.setPlayerCharacter(null); + } + } + + protected static void remAccount(Session s) { + if (s.getAccount() != null) { + SessionManager.accountToSession.remove(s.getAccount()); + s.setAccount(null); + } + } + + protected static void remSessionID(Session s) { + + if (s.getSessionID() != null) { + SessionManager.sessionIDtoSession.remove(s.getSessionID()); + s.setSessionID(null); + } + } + + protected static void remClientConnection(Session s) { + if (s.getConn() != null) { + SessionManager.connToSession.remove(s.getConn()); + s.setConn(null); + } + } + + + + /* + * Utils + */ + + public static void setCrossServerBehavior(int type) { + crossServerBehavior = type; + } + + /** + * @return a new HashSet object so the caller cannot + * modify the internal Set + */ + public static Collection getAllActiveClientConnections() { + return SessionManager.connToSession.keySet(); + } + + /** + * @return a new HashSet object so the caller cannot modify + * the internal Set + */ + public static Collection getAllActivePlayerCharacters() { + + return SessionManager.pcToSession.keySet(); + } + + public static Collection getAllActivePlayers() { + + return SessionManager.pcToSession.keySet(); + } + + public static int getActivePlayerCharacterCount() { + + return SessionManager.pcToSession.keySet().size(); + } + + public static ArrayList getActivePCsInGuildID(int id) { + ArrayList pcs = new ArrayList<>(); + + for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) { + Guild g = pc.getGuild(); + if (g != null && g.getObjectUUID() == id) { + pcs.add(pc); + } + } + + return pcs; + } + + public static PlayerCharacter getPlayerCharacterByLowerCaseName(String name) { + + String queryName = name.toLowerCase(); + + for (PlayerCharacter playerCharacter : SessionManager.getAllActivePlayerCharacters()) { + + if ((playerCharacter.getFirstName().toLowerCase()).equals(queryName)) { + return playerCharacter; + } + } + return null; + } + + public static PlayerCharacter getPlayerCharacterByID(int UUID) { + + for (PlayerCharacter playerCharacter : SessionManager.getAllActivePlayerCharacters()) { + + if (playerCharacter.getObjectUUID() == UUID) { + return playerCharacter; + } + } + return null; + } + + public static Collection getAllActiveAccounts() { + return SessionManager.accountToSession.keySet(); + } + + public static Account getAccountByID(int UUID) { + + for (Account acc : SessionManager.getAllActiveAccounts()) { + + if (acc.getObjectUUID() == UUID) + return acc; + + } + return null; + } +} diff --git a/src/engine/gameManager/SimulationManager.java b/src/engine/gameManager/SimulationManager.java new file mode 100644 index 00000000..ccdcb007 --- /dev/null +++ b/src/engine/gameManager/SimulationManager.java @@ -0,0 +1,223 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.objects.AbstractGameObject; +import engine.objects.City; +import engine.objects.PlayerCharacter; +import engine.objects.Runegate; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; + +/* + * This class contains all methods necessary to drive periodic + * updates of the game simulation from the main _exec loop. + */ +public enum SimulationManager { + + SERVERHEARTBEAT; + + private static final long CITY_PULSE = 2000; + private static final long RUNEGATE_PULSE = 3000; + private static final long UPDATE_PULSE = 1000; + private static final long FlIGHT_PULSE = 100; + public static Duration executionTime = Duration.ofNanos(1); + public static Duration executionMax = Duration.ofNanos(1); + private static SimulationManager instance = null; + private long _cityPulseTime = System.currentTimeMillis() + CITY_PULSE; + private long _runegatePulseTime = System.currentTimeMillis() + + RUNEGATE_PULSE; + private long _updatePulseTime = System.currentTimeMillis() + UPDATE_PULSE; + private long _flightPulseTime = System.currentTimeMillis() + FlIGHT_PULSE; + + private SimulationManager() { + + // don't allow instantiation. + } + + public static String getPopulationString() { + + String popString = ""; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement getPopString = connection.prepareStatement("CALL GET_POPULATION_STRING()");) { + + ResultSet rs = getPopString.executeQuery(); + + if (rs.next()) + popString = rs.getString("popstring"); + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + return popString; + } + + /* + * Update the simulation. *** Important: Whatever you do in here, do it damn + * quick! + */ + public void tick() { + + /* + * As we're on the main thread we must be sure to catch any possible + * errors. + * + * IF something does occur, disable that particular heartbeat. Better + * runegates stop working than the game itself! + */ + + Instant startTime = Instant.now(); + + try { + if ((_flightPulseTime != 0) + && (System.currentTimeMillis() > _flightPulseTime)) + pulseFlight(); + } catch (Exception e) { + Logger.error( + "Fatal error in City Pulse: DISABLED. Error Message : " + + e.getMessage()); + } + try { + + if ((_updatePulseTime != 0) + && (System.currentTimeMillis() > _updatePulseTime)) + pulseUpdate(); + } catch (Exception e) { + Logger.error( + "Fatal error in Update Pulse: DISABLED"); + // _runegatePulseTime = 0; + } + + try { + if ((_runegatePulseTime != 0) + && (System.currentTimeMillis() > _runegatePulseTime)) + pulseRunegates(); + } catch (Exception e) { + Logger.error( + "Fatal error in Runegate Pulse: DISABLED"); + _runegatePulseTime = 0; + } + + try { + if ((_cityPulseTime != 0) + && (System.currentTimeMillis() > _cityPulseTime)) + pulseCities(); + } catch (Exception e) { + Logger.error( + "Fatal error in City Pulse: DISABLED. Error Message : " + + e.getMessage()); + e.printStackTrace(); + + } + + SimulationManager.executionTime = Duration.between(startTime, Instant.now()); + + if (executionTime.compareTo(executionMax) > 0) + executionMax = executionTime; + } + + /* + * Mainline simulation update method: handles movement and regen for all + * player characters + */ + + private void pulseUpdate() { + + Collection playerList; + + playerList = DbManager.getList(GameObjectType.PlayerCharacter); + + // Call update() on each player in game + + if (playerList == null) + return; + + for (AbstractGameObject ago : playerList) { + PlayerCharacter player = (PlayerCharacter) ago; + + if (player == null) + continue; + player.update(); + } + + _updatePulseTime = System.currentTimeMillis() + 500; + } + + private void pulseFlight() { + + Collection playerList; + + playerList = DbManager.getList(GameObjectType.PlayerCharacter); + + // Call update() on each player in game + + if (playerList == null) + return; + + for (AbstractGameObject ago : playerList) { + PlayerCharacter player = (PlayerCharacter) ago; + + if (player == null) + continue; + + + player.updateFlight(); + } + + _flightPulseTime = System.currentTimeMillis() + FlIGHT_PULSE; + } + + private void pulseCities() { + + City city; + + // *** Refactor: Need a list cached somewhere as it doesn't change very + // often at all. Have a cityListIsDirty boolean that gets set if it + // needs an update. Will speed up this method a great deal. + + Collection cityList = DbManager.getList(Enum.GameObjectType.City); + + if (cityList == null) { + Logger.info("City List null"); + return; + } + + for (AbstractGameObject cityObject : cityList) { + city = (City) cityObject; + city.onEnter(); + } + + _cityPulseTime = System.currentTimeMillis() + CITY_PULSE; + } + + /* + * Method runs proximity collision detection for all active portals on the + * game's Runegates + */ + private void pulseRunegates() { + + for (Runegate runegate : Runegate._runegates.values()) { + runegate.collidePortals(); + } + + _runegatePulseTime = System.currentTimeMillis() + RUNEGATE_PULSE; + + } +} diff --git a/src/engine/gameManager/TradeManager.java b/src/engine/gameManager/TradeManager.java new file mode 100644 index 00000000..15d6e2df --- /dev/null +++ b/src/engine/gameManager/TradeManager.java @@ -0,0 +1,161 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.CharacterItemManager; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +public enum TradeManager { + + TRADEMANAGER; + + public static void tradeRequest(TradeRequestMsg msg, ClientConnection origin) { + + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null) + return; + + source.getCharItemManager().tradeRequest(msg); + + } + + + public static void acceptTradeRequest(AcceptTradeRequestMsg msg, ClientConnection origin) { + + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null) + return; + + try { + source.getCharItemManager().acceptTradeRequest(msg); + } catch (Exception e) { + Logger.error(e); + // TODO Auto-generated catch block + } + + } + + public static void rejectTradeRequest(RejectTradeRequestMsg msg, ClientConnection origin) { + // TODO Do nothing? If so, delete this method & case above + } + + public static void addItemToTradeWindow(AddItemToTradeWindowMsg msg, ClientConnection origin) { + + + PlayerCharacter source = origin.getPlayerCharacter(); + if (source == null || !source.isAlive()) + return; + try { + source.getCharItemManager().addItemToTradeWindow(msg); + + } catch (Exception e) { + Logger.error(e); + } + + } + + public static void addGoldToTradeWindow(AddGoldToTradeWindowMsg msg, ClientConnection origin) { + + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null || !source.isAlive()) + return; + + + CharacterItemManager sourceItemMan = source.getCharItemManager(); + + if (sourceItemMan == null) + return; + + try { + sourceItemMan.addGoldToTradeWindow(msg); + } catch (Exception e) { + Logger.error(e); + } + } + + public static void commitToTrade(CommitToTradeMsg msg, ClientConnection origin) { + + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null || !source.isAlive()) + return; + + CharacterItemManager sourceItemMan = source.getCharItemManager(); + + if (sourceItemMan == null) + return; + + try { + sourceItemMan.commitToTrade(msg); + } catch (Exception e) { + // TODO Auto-generated catch block + Logger.error(e); + } + } + + public static void uncommitToTrade(UncommitToTradeMsg msg, ClientConnection origin) { + + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null || !source.isAlive()) + return; + + CharacterItemManager sourceItemMan = source.getCharItemManager(); + + if (sourceItemMan == null) + return; + + try { + sourceItemMan.uncommitToTrade(msg); + } catch (Exception e) { + // TODO Auto-generated catch block + Logger.error(e); + } + + } + + + public static void closeTradeWindow(CloseTradeWindowMsg msg, ClientConnection origin) { + + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null) + return; + + CharacterItemManager sourceItemMan = source.getCharItemManager(); + + if (sourceItemMan == null) + return; + + try { + sourceItemMan.closeTradeWindow(msg, true); + } catch (Exception e) { + // TODO Auto-generated catch block + Logger.error(e); + } + } + + public static void invalidTradeRequest(InvalidTradeRequestMsg msg) { + PlayerCharacter requester = PlayerCharacter.getFromCache(msg.getRequesterID()); + Dispatch dispatch; + + dispatch = Dispatch.borrow(requester, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } +} \ No newline at end of file diff --git a/src/engine/gameManager/ZoneManager.java b/src/engine/gameManager/ZoneManager.java new file mode 100644 index 00000000..8802744e --- /dev/null +++ b/src/engine/gameManager/ZoneManager.java @@ -0,0 +1,456 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.gameManager; + +import engine.Enum; +import engine.db.archive.CityRecord; +import engine.db.archive.DataWarehouse; +import engine.math.Bounds; +import engine.math.Vector2f; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.Zone; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +/* + * Class contains methods and structures which + * track in-game Zones + */ +public enum ZoneManager { + + ZONEMANAGER; + + public static final Set macroZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private static final ConcurrentHashMap zonesByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); + private static final ConcurrentHashMap zonesByUUID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); + private static final ConcurrentHashMap zonesByName = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); + private static final Set npcCityZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private static final Set playerCityZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); + public static Instant hotZoneLastUpdate; + public static Zone hotZone = null; + public static int hotZoneCycle = 0; // Used with HOTZONE_DURATION from config. + public static HashMap _zone_size_data = new HashMap<>(); + /* Instance variables */ + private static Zone seaFloor = null; + + // Find all zones coordinates fit into, starting with Sea Floor + + public static ArrayList getAllZonesIn(final Vector3fImmutable loc) { + + ArrayList allIn = new ArrayList<>(); + Zone zone; + + zone = ZoneManager.findSmallestZone(loc); + + if (zone != null) { + allIn.add(zone); + while (zone.getParent() != null) { + zone = zone.getParent(); + allIn.add(zone); + } + } + return allIn; + } + + // Find smallest zone coordinates fit into. + + public static final Zone findSmallestZone(final Vector3fImmutable loc) { + + Zone zone = ZoneManager.seaFloor; + + if (zone == null) + return null; + + boolean childFound = true; + + while (childFound) { + + childFound = false; + + ArrayList nodes = zone.getNodes(); + + // Logger.info("soze", "" + nodes.size()); + if (nodes != null) + for (Zone child : nodes) { + + if (Bounds.collide(loc, child.getBounds()) == true) { + zone = child; + childFound = true; + break; + } + } + } + return zone; + } + + public static void addZone(final int zoneID, final Zone zone) { + + ZoneManager.zonesByID.put(zoneID, zone); + + ZoneManager.zonesByUUID.put(zone.getObjectUUID(), zone); + + ZoneManager.zonesByName.put(zone.getName().toLowerCase(), zone); + + } + + // Returns the number of available hotZones + // remaining in this cycle (1am) + + public static int availableHotZones() { + + int count = 0; + + for (Zone zone : ZoneManager.macroZones) + if (ZoneManager.validHotZone(zone)) + count = count + 1; + + return count; + } + + // Resets the availability of hotZones + // for this cycle + + public static void resetHotZones() { + + for (Zone zone : ZoneManager.macroZones) + if (zone.hasBeenHotzone) + zone.hasBeenHotzone = false; + + } + + public static Zone getZoneByUUID(final int zoneUUID) { + return ZoneManager.zonesByUUID.get(zoneUUID); + } + + public static Zone getZoneByZoneID(final int zoneID) { + + return ZoneManager.zonesByID.get(zoneID); + } + + public static Zone getZoneByName(final String zoneName) { + return ZoneManager.zonesByName.get(zoneName); + } + + public static final Collection getAllZones() { + return ZoneManager.zonesByUUID.values(); + } + + public static final void setHotZone(final Zone zone) { + + if (!zone.isMacroZone()) + return; + + ZoneManager.hotZone = zone; + ZoneManager.hotZoneCycle = 1; // Used with HOTZONE_DURATION from config. + zone.hasBeenHotzone = true; + ZoneManager.hotZoneLastUpdate = LocalDateTime.now().withMinute(0).withSecond(0).atZone(ZoneId.systemDefault()).toInstant(); + + } + + public static boolean inHotZone(final Vector3fImmutable loc) { + + if (ZoneManager.hotZone == null) + return false; + + return (Bounds.collide(loc, ZoneManager.hotZone.getBounds()) == true); + } + + public static Zone getSeaFloor() { + return ZoneManager.seaFloor; + } + + public static void setSeaFloor(final Zone value) { + ZoneManager.seaFloor = value; + } + + public static final void populateWorldZones(final Zone zone) { + + int loadNum = zone.getLoadNum(); + + // Zones are added to separate + // collections for quick access + // based upon their type. + + if (zone.isMacroZone()) { + addMacroZone(zone); + return; + } + + + if (zone.isPlayerCity()) { + addPlayerCityZone(zone); + return; + } + + if (zone.isNPCCity()) + addNPCCityZone(zone); + + } + + private static void addMacroZone(final Zone zone) { + ZoneManager.macroZones.add(zone); + } + + private static void addNPCCityZone(final Zone zone) { + zone.setNPCCity(true); + ZoneManager.npcCityZones.add(zone); + } + + public static final void addPlayerCityZone(final Zone zone) { + zone.setPlayerCity(true); + ZoneManager.playerCityZones.add(zone); + } + + public static final void generateAndSetRandomHotzone() { + + Zone hotZone; + ArrayList zoneArray = new ArrayList<>(); + + if (ZoneManager.macroZones.isEmpty()) + return; + + // Reset hotZone availability if none are left. + + if (ZoneManager.availableHotZones() == 0) + ZoneManager.resetHotZones(); + + for (Zone zone : ZoneManager.macroZones) + if (validHotZone(zone)) + zoneArray.add(zone.getObjectUUID()); + + int entryIndex = ThreadLocalRandom.current().nextInt(zoneArray.size()); + + hotZone = ZoneManager.getZoneByUUID(zoneArray.get(entryIndex)); + + if (hotZone == null) { + Logger.error("Hotzone is null"); + return; + } + + ZoneManager.setHotZone(hotZone); + + } + + public static final boolean validHotZone(Zone zone) { + + if (zone.getSafeZone() == (byte) 1) + return false; // no safe zone hotzones// if (this.hotzone == null) + + if (zone.getNodes().isEmpty()) + return false; + + if (zone.equals(ZoneManager.seaFloor)) + return false; + + //no duplicate hotZones + + if (zone.hasBeenHotzone == true) + return false; + + // Enforce min level + + if (zone.minLvl < Integer.parseInt(ConfigManager.MB_HOTZONE_MIN_LEVEL.getValue())) + return false; + + if (ZoneManager.hotZone != null) + return ZoneManager.hotZone.getObjectUUID() != zone.getObjectUUID(); + + return true; + } + + // Converts world coordinates to coordinates local to a given zone. + + public static Vector3fImmutable worldToLocal(Vector3fImmutable worldVector, + Zone serverZone) { + + Vector3fImmutable localCoords; + + localCoords = new Vector3fImmutable(worldVector.x - serverZone.absX, + worldVector.y - serverZone.absY, worldVector.z + - serverZone.absZ); + + return localCoords; + } + + public static Vector2f worldToZoneSpace(Vector3fImmutable worldVector, + Zone serverZone) { + + Vector2f localCoords; + Vector2f zoneOrigin; + + // Top left corner of zone is calculated in world space by the center and it's extents. + + zoneOrigin = new Vector2f(serverZone.getLoc().x, serverZone.getLoc().z); + zoneOrigin = zoneOrigin.subtract(new Vector2f(serverZone.getBounds().getHalfExtents().x, serverZone.getBounds().getHalfExtents().y)); + + // Local coordinate in world space translated to an offset from the calculated zone origin. + + localCoords = new Vector2f(worldVector.x, worldVector.z); + localCoords = localCoords.subtract(zoneOrigin); + + localCoords.setY((serverZone.getBounds().getHalfExtents().y * 2) - localCoords.y); + + + // TODO : Make sure this value does not go outside the zone's bounds. + + return localCoords; + } + + // Converts local zone coordinates to world coordinates + + public static Vector3fImmutable localToWorld(Vector3fImmutable worldVector, + Zone serverZone) { + + Vector3fImmutable worldCoords; + + worldCoords = new Vector3fImmutable(worldVector.x + serverZone.absX, + worldVector.y + serverZone.absY, worldVector.z + + serverZone.absZ); + + return worldCoords; + } + + + /** + * Converts from local (relative to this building) to world. + * + * @param localPos position in local reference (relative to this building) + * @return position relative to world + */ + + public static Vector3fImmutable convertLocalToWorld(Building building, Vector3fImmutable localPos) { + + // convert from SB rotation value to radians + + if (building.getBounds().getQuaternion() == null) + return building.getLoc(); + + Vector3fImmutable rotatedLocal = Vector3fImmutable.rotateAroundPoint(Vector3fImmutable.ZERO, localPos, building.getBounds().getQuaternion()); + + // handle building rotation + // handle building translation + + return building.getLoc().add(rotatedLocal.x, rotatedLocal.y, rotatedLocal.z); + } + + + //used for regions, Building bounds not set yet. + public static Vector3f convertLocalToWorld(Building building, Vector3f localPos, Bounds bounds) { + + // convert from SB rotation value to radians + + + Vector3f rotatedLocal = Vector3f.rotateAroundPoint(Vector3f.ZERO, localPos, bounds.getQuaternion()); + + // handle building rotation + // handle building translation + + return new Vector3f(building.getLoc().add(rotatedLocal.x, rotatedLocal.y, rotatedLocal.z)); + } + + public static Vector3fImmutable convertWorldToLocal(Building building, Vector3fImmutable WorldPos) { + Vector3fImmutable convertLoc = Vector3fImmutable.rotateAroundPoint(building.getLoc(), WorldPos, -building.getBounds().getQuaternion().angleY); + + convertLoc = convertLoc.subtract(building.getLoc()); + + // convert from SB rotation value to radians + + return convertLoc; + + } + + // Method returns a city if the given location is within + // a city zone. + + public static City getCityAtLocation(Vector3fImmutable worldLoc) { + + Zone currentZone; + ArrayList zoneList; + City city; + + currentZone = ZoneManager.findSmallestZone(worldLoc); + + if (currentZone.isPlayerCity()) + return City.getCity(currentZone.getPlayerCityUUID()); + + return null; + } + + /* Method is called when creating a new player city to + * validate that the new zone does not overlap any other + * zone that might currently exist + */ + + public static boolean validTreePlacementLoc(Zone currentZone, float positionX, float positionZ) { + + // Member Variable declaration + + ArrayList zoneList; + boolean validLocation = true; + Bounds treeBounds; + + if (currentZone.isContinent() == false) + return false; + + + treeBounds = Bounds.borrow(); + treeBounds.setBounds(new Vector2f(positionX, positionZ), new Vector2f(Enum.CityBoundsType.PLACEMENT.extents, Enum.CityBoundsType.PLACEMENT.extents), 0.0f); + + zoneList = currentZone.getNodes(); + + for (Zone zone : zoneList) { + + if (zone.isContinent()) + continue; + + if (Bounds.collide(treeBounds, zone.getBounds(), 0.0f)) + validLocation = false; + } + + treeBounds.release(); + return validLocation; + } + + public static void loadCities(Zone zone) { + + ArrayList cities = DbManager.CityQueries.GET_CITIES_BY_ZONE(zone.getObjectUUID()); + + for (City city : cities) { + + city.setParent(zone); + city.setObjectTypeMask(MBServerStatics.MASK_CITY); + city.setLoc(city.getLoc()); // huh? + +//not player city, must be npc city.. + + if (!zone.isPlayerCity()) + zone.setNPCCity(true); + + if ((ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER)) && (city.getHash() == null)) { + + city.setHash(); + + if (DataWarehouse.recordExists(Enum.DataRecordType.CITY, city.getObjectUUID()) == false) { + CityRecord cityRecord = CityRecord.borrow(city, Enum.RecordEventType.CREATE); + DataWarehouse.pushToWarehouse(cityRecord); + } + } + } + } +} diff --git a/src/engine/job/AbstractJob.java b/src/engine/job/AbstractJob.java new file mode 100644 index 00000000..3d2ed162 --- /dev/null +++ b/src/engine/job/AbstractJob.java @@ -0,0 +1,153 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + +import org.pmw.tinylog.Logger; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + + +/** + * Provides mandatory base implementation for all 'Job's'. + * + * @author + */ +public abstract class AbstractJob implements Runnable { + + private static final String DEFAULT_WORKERID = "UNPROCESSED_JOB"; + + ; + private final AtomicReference workerID; + + ; + /** + * Keep fields private. All access through getters n setters for Thread + * Safety. + */ + private JobRunStatus runStatus; + private JobCompletionStatus completeStatus; + private UUID uuid = null; + private long submitTime = 0L; + private long startTime = 0L; + private long stopTime = 0L; + + public AbstractJob() { + //Tests against DEFAULT_WORKERID to ensure single execution + this.workerID = new AtomicReference<>(DEFAULT_WORKERID); + this.runStatus = JobRunStatus.RUNNING; + this.completeStatus = JobCompletionStatus.NOTCOMPLETEDYET; + this.uuid = UUID.randomUUID(); + } + + @Override + public void run() { + + if (workerID.get().equals(DEFAULT_WORKERID)) { + Logger.warn("FIX ME! Job ran through 'run()' directly. Use executeJob(String) instead."); + } + + this.markStartRunTime(); + + this.setRunStatus(JobRunStatus.RUNNING); + try { + this.doJob(); + } catch (Exception e) { + Logger.error(e); + } + + this.setRunStatus(JobRunStatus.FINISHED); + + this.markStopRunTime(); + } + + protected abstract void doJob(); + + public void executeJob(String threadName) { + this.workerID.set(threadName); + this.run(); + } + + public JobRunStatus getRunStatus() { + synchronized (this.runStatus) { + return runStatus; + } + } + + protected void setRunStatus(JobRunStatus status) { + synchronized (this.runStatus) { + this.runStatus = status; + } + } + + public JobCompletionStatus getCompletionStatus() { + synchronized (this.completeStatus) { + return completeStatus; + } + } + + protected void setCompletionStatus(JobCompletionStatus status) { + synchronized (this.completeStatus) { + this.completeStatus = status; + } + } + + public UUID getJobId() { + synchronized (this.uuid) { + return uuid; + } + } + + protected void setJobId(UUID id) { + synchronized (this.uuid) { + this.uuid = id; + } + } + + public String getWorkerID() { + return workerID.get(); + } + + protected void markSubmitTime() { + this.submitTime = System.currentTimeMillis() - 2; + } + + protected void markStartRunTime() { + this.startTime = System.currentTimeMillis() - 1; + } + + /* + * Time markers + */ + + protected void markStopRunTime() { + this.stopTime = System.currentTimeMillis(); + } + + public final long getSubmitTime() { + return submitTime; + } + + public final long getStartTime() { + return startTime; + } + + public final long getStopTime() { + return stopTime; + } + + public enum JobRunStatus { + CREATED, RUNNING, FINISHED; + } + + public enum JobCompletionStatus { + NOTCOMPLETEDYET, SUCCESS, SUCCESSWITHERRORS, FAIL; + } +} diff --git a/src/engine/job/AbstractJobStatistics.java b/src/engine/job/AbstractJobStatistics.java new file mode 100644 index 00000000..b88c02e7 --- /dev/null +++ b/src/engine/job/AbstractJobStatistics.java @@ -0,0 +1,118 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + +public abstract class AbstractJobStatistics { + + private String objectName; + private long totalServiceTime = 0L; + private long totalQueueTime = 0L; + private long executions = 0L; + private long maxServiceTime = 0L; + private long minServiceTime = 0L; + private long minQueueTime = 0L; + private long maxQueueTime = 0L; + + + public AbstractJobStatistics() { + this.objectName = "Unknown"; + } + + public AbstractJobStatistics(String objectName) { + this.objectName = objectName; + } + + public String getObjectName() { + return this.objectName; + } + + public void setObjectName(String objectName) { + this.objectName = objectName; + } + + public long getExecutions() { + return this.executions; + } + + public long getTotalServiceTime() { + return this.totalServiceTime; + } + + public long getTotalQueueTime() { + return this.totalQueueTime; + } + + public long getAvgQueueTime() { + if (this.executions > 0L && this.totalQueueTime > 0L) + return this.totalQueueTime / this.executions; + else + return 0L; + } + + public long getAvgServiceTime() { + if (this.executions > 0L && this.totalServiceTime > 0L) + return this.totalServiceTime / this.executions; + else + return 0L; + } + + public long getMinServiceTime() { + return this.minServiceTime; + } + + public long getMinQueueTime() { + return this.minQueueTime; + } + + public long getMaxServiceTime() { + return this.maxServiceTime; + } + + public long getMaxQueueTime() { + return this.maxQueueTime; + } + + public void incrExecutions() { + this.executions++; + } + + public void addServiceTime(long svcTime) { + this.totalServiceTime += svcTime; + this.incrExecutions(); + if (svcTime > this.maxServiceTime) + this.maxServiceTime = svcTime; + if (svcTime < this.minServiceTime || this.minServiceTime == 0L) + this.minServiceTime = svcTime; + + } + + public void addQueueTime(long queueTime) { + this.totalQueueTime += queueTime; + this.incrExecutions(); + if (queueTime > this.maxQueueTime) + this.maxQueueTime = queueTime; + if (queueTime < this.minQueueTime || this.minQueueTime == 0L) + this.minQueueTime = queueTime; + + } + + public String asString() { + return this.objectName + " execs=" + this.executions + " avg_svc_ms=" + this.getAvgServiceTime() + + " min_svc_ms=" + this.minServiceTime + " max_svc_ms=" + this.maxServiceTime + + " avg_q_ms=" + this.getAvgQueueTime() + " min_q_ms=" + this.minQueueTime + + " max_q_ms=" + this.maxQueueTime; + } + + public String asChatMsg() { + return this.objectName + "=>" + this.executions + ',' + this.getAvgServiceTime() + '/' + this.minServiceTime + + '/' + this.maxServiceTime + " " + this.getAvgQueueTime() + '/' + + this.minQueueTime + '/' + this.maxQueueTime + '\n'; + } +} diff --git a/src/engine/job/AbstractScheduleJob.java b/src/engine/job/AbstractScheduleJob.java new file mode 100644 index 00000000..3ba8a494 --- /dev/null +++ b/src/engine/job/AbstractScheduleJob.java @@ -0,0 +1,28 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + + +public abstract class AbstractScheduleJob extends AbstractJob { + + public AbstractScheduleJob() { + super(); + } + + @Override + protected abstract void doJob(); + + public void cancelJob() { + JobScheduler.getInstance().cancelScheduledJob(this); + _cancelJob(); + } + + protected abstract void _cancelJob(); +} diff --git a/src/engine/job/ClassJobStatistics.java b/src/engine/job/ClassJobStatistics.java new file mode 100644 index 00000000..e27d9878 --- /dev/null +++ b/src/engine/job/ClassJobStatistics.java @@ -0,0 +1,19 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + +public class ClassJobStatistics extends AbstractJobStatistics { + + public ClassJobStatistics(String className) { + super(className); + } + + +} diff --git a/src/engine/job/JobContainer.java b/src/engine/job/JobContainer.java new file mode 100644 index 00000000..181e85cd --- /dev/null +++ b/src/engine/job/JobContainer.java @@ -0,0 +1,84 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + + +public class JobContainer implements Comparable { + + final AbstractJob job; + final long timeOfExecution; + final boolean noTimer; + + JobContainer(AbstractJob job, long timeOfExecution) { + if (job == null) { + throw new IllegalArgumentException("No 'null' jobs allowed."); + } + this.job = job; + this.timeOfExecution = timeOfExecution; + this.noTimer = false; + } + + public JobContainer(AbstractJob job) { + if (job == null) { + throw new IllegalArgumentException("No 'null' jobs allowed."); + } + this.job = job; + this.timeOfExecution = Long.MAX_VALUE; + this.noTimer = true; + } + + public AbstractJob getJob() { + return job; + } + + public boolean noTimer() { + return noTimer; + } + + public long timeOfExection() { + return this.timeOfExecution; + } + + public int timeToExecutionLeft() { + if (JobScheduler.getInstance().isAlive()) { + int timeLeft = (int) (timeOfExecution - System.currentTimeMillis()); + if (timeLeft < 0) + timeLeft = 0; + return timeLeft; + } else + return (int) (timeOfExecution - JobScheduler.getInstance().getTimeOfKill()); + } + + @Override + public int compareTo(JobContainer compared) { + if (timeOfExecution < compared.timeOfExecution) { + return -1; + } + if (timeOfExecution > compared.timeOfExecution) { + return 1; + } + return 0; + } + + @Override + public boolean equals(Object obj) { + return job.equals(((JobContainer) obj).job); + } + + @Override + public int hashCode() { + return job.hashCode(); + } + + public void cancelJob() { + if (job != null && job instanceof AbstractScheduleJob) + ((AbstractScheduleJob) job).cancelJob(); + } +} diff --git a/src/engine/job/JobManager.java b/src/engine/job/JobManager.java new file mode 100644 index 00000000..da40b030 --- /dev/null +++ b/src/engine/job/JobManager.java @@ -0,0 +1,230 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + +import engine.core.ControlledRunnable; +import engine.server.MBServerStatics; +import engine.util.ThreadUtils; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * Note to DEVs. When attempting to log something in this class, use logDIRECT + * only. + */ +public class JobManager extends ControlledRunnable { + + /* + * Singleton implementation. + */ + private static volatile JobManager INSTANCE; + private final ArrayList jobPoolList = new ArrayList<>(); + + /* + * Class implementation + */ + private final ConcurrentHashMap jobQueueMapping = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + private boolean shutdownNowFlag = false; + + private JobManager() { + super("JobManager"); + + // create the initial job pools with the correct sizes + // based on the number of array elements in the initial_jo_workers + // definition in Statisc + + if (MBServerStatics.INITIAL_JOBPOOL_WORKERS != null && MBServerStatics.INITIAL_JOBPOOL_WORKERS.length > 0) { + for (int i = 0; i < MBServerStatics.INITIAL_JOBPOOL_WORKERS.length; i++) { + JobPool jp = new JobPool(i, MBServerStatics.INITIAL_JOBPOOL_WORKERS[i]); + this.jobPoolList.add(jp); + Logger.info("Adding JobPool_" + jp.getJobPoolID() + " with " + MBServerStatics.INITIAL_JOBPOOL_WORKERS[i] + " workers"); + } + } else { + // not defined or empty in statics so just create 1 JobPool with 25 workers + System.out.println("Creating 1 pool called 0"); + JobPool jp = new JobPool(0, 25); + this.jobPoolList.add(jp); + Logger.info("Adding JobPool_" + jp.getJobPoolID() + " with 25 workers"); + } + + JobScheduler.getInstance(); + + // if you want any jobs to default onto a given queue put it here + // otherwise everything goes on the P1 queue by default + + } + + public static JobManager getInstance() { + if (JobManager.INSTANCE == null) { + synchronized (JobManager.class) { + if (JobManager.INSTANCE == null) { + JobManager.INSTANCE = new JobManager(); + JobManager.INSTANCE.startup(); + } + } + } + return JobManager.INSTANCE; + } + + /** + * Submit a job to be processed by the JobManager. There is no guarantee + * that the job will be executed immediately. + * + * @param aj AbstractJob to be submitted. + * @return boolean value indicating whether the job was submitted or not. + */ + public boolean submitJob(AbstractJob aj) { + + if (jobQueueMapping.containsKey(aj.getClass().getSimpleName())) { + return this.jobQueueMapping.get(aj.getClass().getSimpleName()).submitJob(aj); + } else { + return this.jobPoolList.get(0).submitJob(aj); + } + } + + + private void auditAllWorkers() { + + for (JobPool jp : this.jobPoolList) { + jp.auditWorkers(); + } + } + + @Override + protected boolean _Run() { + // This thread is to be used to JM internal monitoring. + + // Startup workers: + this.auditAllWorkers(); + ThreadUtils.sleep(MBServerStatics.JOBMANAGER_INTERNAL_MONITORING_INTERVAL_MS * 2); + + this.runStatus = true; + + // Monitoring loop + while (this.runCmd) { + this.auditAllWorkers(); + ThreadUtils.sleep(MBServerStatics.JOBMANAGER_INTERNAL_MONITORING_INTERVAL_MS); + } + + // No new submissions + for (JobPool jp : this.jobPoolList) { + jp.setBlockNewSubmissions(true); + } + + if (this.shutdownNowFlag == false) { + // shutdown each pool + for (JobPool jp : this.jobPoolList) { + jp.shutdown(); + } + } else { + // Emergency Stop each pool + for (JobPool jp : this.jobPoolList) { + jp.emergencyStop(); + } + } + this.runStatus = false; + Logger.info("JM Shutdown"); + return true; + } + + @Override + protected boolean _postRun() { + return true; + } + + @Override + protected boolean _preRun() { + return true; + } + + @Override + protected void _shutdown() { + Logger.info("JM Shutdown Requested"); + JobScheduler.getInstance().shutdown(); + } + + @Override + protected void _startup() { + Logger.info("JM Starting up... "); + } + + public void shutdownNow() { + this.shutdownNowFlag = true; + this.shutdown(); + } + + public String moveJob(String simpleClassName, String jobPoolID) { + // moves a job to a different queue + + try { + // parse string into an int + Integer i = Integer.parseInt(jobPoolID); + for (JobPool jp : this.jobPoolList) { + if (jp.getJobPoolID() == i) { + // move the job to the new queue + this.jobQueueMapping.put(simpleClassName, jp); + return simpleClassName + " moved to JobPool ID " + jp.getJobPoolID(); + } + } + + } catch (NumberFormatException e) { + return "jobPoolID is not a valid number"; + } + + //Verify jobpool ID + return "Invalid parameters required"; + } + + public String resetJobs() { + // moves all jobs to the P1 queue + this.jobQueueMapping.clear(); + return "All Jobs reset onto P1 queue"; + } + + public String showJobs() { + String out = ""; + Iterator jmi = this.jobQueueMapping.keySet().iterator(); + + while (jmi.hasNext()) { + String jmiKey = jmi.next(); + out += jmiKey + ' ' + this.jobQueueMapping.get(jmiKey).getJobPoolID() + '\n'; + } + return out; + } + + public ArrayList getJobPoolList() { + + return this.jobPoolList; + } + + public String modifyJobPoolWorkers(String jobPoolID, String maxWorkers) { + + try { + // parse string into an int + Integer jid = Integer.parseInt(jobPoolID); + Integer mw = Integer.parseInt(maxWorkers); + for (JobPool jp : this.jobPoolList) { + if (jp.getJobPoolID() == jid) { + // change the number of workers + return jp.setMaxWorkers(mw); + } + } + + } catch (NumberFormatException e) { + return "Invalid parameters required"; + } + + return "Invalid parameters required"; + } +} diff --git a/src/engine/job/JobPool.java b/src/engine/job/JobPool.java new file mode 100644 index 00000000..9a96d59b --- /dev/null +++ b/src/engine/job/JobPool.java @@ -0,0 +1,295 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + + +import engine.jobs.AttackJob; +import engine.jobs.UsePowerJob; +import engine.net.CheckNetMsgFactoryJob; +import engine.net.ConnectionMonitorJob; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +public class JobPool { + + private final LinkedBlockingQueue jobWaitQueue = new LinkedBlockingQueue<>(); + private final LinkedBlockingQueue jobWorkerQueue = new LinkedBlockingQueue<>(); + private final ArrayList jobWorkerList = new ArrayList<>(); + private final LinkedBlockingQueue jobRunList = new LinkedBlockingQueue<>(); + int jobPoolID; + int maxWorkers; + int nextWorkerID; + private boolean blockNewSubmissions = false; + + public JobPool(int id, int workers) { + this.jobPoolID = id; + + // default to 1 unless workers parameter is higher + int actualWorkers = 1; + if (workers > 1) + actualWorkers = workers; + + this.maxWorkers = actualWorkers; + for (int i = 0; i < actualWorkers; i++) { + this.startWorker(i); + } + + this.nextWorkerID = this.maxWorkers; + + + } + + private static boolean isExemptJobFromAudit(AbstractJob aj) { + // If the job is any of the following classes, exempt it from auditWorkers + if (aj instanceof ConnectionMonitorJob) { + return true; + } else + return aj instanceof CheckNetMsgFactoryJob || aj instanceof AttackJob || aj instanceof UsePowerJob; + + } + + private int getNextWorkerID() { + return this.nextWorkerID++; + + + } + + public int getJobPoolID() { + return this.jobPoolID; + } + + public void setBlockNewSubmissions(boolean blocked) { + this.blockNewSubmissions = blocked; + } + + public boolean submitJob(AbstractJob aj) { + + if (blockNewSubmissions) { + Logger.warn("A '" + aj.getClass().getSimpleName() + "' job was submitted, but submissions are currently blocked."); + return false; + } + + aj.markSubmitTime(); + jobWaitQueue.add(aj); + + // keep notifying workers if the wait queue has items + // commented out as the price of polling the wait queue + // size while it is being updated out-weighs the gain + // for not just blindly waking all workers + // unless we have a stupidly large pool vs CPU threads + + JobWorker jw = jobWorkerQueue.poll(); + if (jw != null) { + synchronized (jw) { + jw.notify(); + } + } + + return true; + } + + private void startWorker(int workerID) { + + // check we dont already have a jobWorker with that ID + synchronized (this.jobWorkerList) { + for (JobWorker jwi : this.jobWorkerList) { + if (jwi.getWorkerId() == workerID) { + Logger.error("Attempt to create worker with ID " + workerID + " failed in JobPool " + jobPoolID + " as worker ID already exists"); + return; + } + } + } + + // ID is unique, create worker + JobWorker jw; + jw = new JobWorker(workerID, this.jobPoolID, this.jobWaitQueue, this.jobWorkerQueue); + + synchronized (this.jobWorkerList) { + //Adds to the overall list.. + jobWorkerList.add(jw); + } + + //Returns to the free worker queue.. + jw.startup(); + } + + private String getQueueByClassAsString(Queue q) { + HashMap ch = new HashMap<>(); + int cnt = 0; + + + // iterate through the linked queue and get every item + // putting classname and incrementing the value each time in the hashmap + Iterator wi = q.iterator(); + + while (cnt < q.size() && wi.hasNext()) { + AbstractJob aj = wi.next(); + if (ch.containsKey(aj.getClass().getSimpleName())) { + int newValue = ch.get(aj.getClass().getSimpleName()) + 1; + ch.put(aj.getClass().getSimpleName(), newValue); + } else { + ch.put(aj.getClass().getSimpleName(), 1); + } + cnt++; + } + + // iterate through the hashmap outputting the classname and number of jobs + Iterator i = ch.keySet().iterator(); + String out = ""; + while (i.hasNext()) { + Object key = i.next(); + out += "JobPoolID_" + this.jobPoolID + ' ' + key.toString() + "=>" + ch.get(key) + '\n'; + } + if (out.isEmpty()) + return "No Jobs on queue\n"; + else + return out; + } + + public void auditWorkers() { + + if (!MBServerStatics.ENABLE_AUDIT_JOB_WORKERS) { + return; + } + ArrayList problemJobs = new ArrayList<>(); + + // Checked for stalled Workers + Iterator it = jobWorkerList.iterator(); + + while (it.hasNext()) { + JobWorker jw = it.next(); + AbstractJob curJob = jw.getCurrentJob(); + + if (curJob != null) { // Has a job + + if (JobPool.isExemptJobFromAudit(curJob)) { + continue; + } + + // Determine whether the job is being executed or waiting to + // start; + + if (curJob.getStartTime() <= 0) { + // Waiting to start + long diff = System.currentTimeMillis() - curJob.getSubmitTime(); + + if (diff >= MBServerStatics.JOB_STALL_THRESHOLD_MS) { + Logger.warn("Job did not start within threshold. Stopping worker#" + jw.getWorkerId() + " JobData:" + + curJob.toString()); + jw.EmergencyStop(); + problemJobs.add(jw.getCurrentJob()); + it.remove(); + } // end if (diff >= + + } else if (curJob.getStopTime() <= 0L) { + // is executing it + long diff = System.currentTimeMillis() - curJob.getStartTime(); + + if (diff >= MBServerStatics.JOB_STALL_THRESHOLD_MS) { + Logger.warn("Job execution time exceeded threshold(" + diff + "). Stopping worker#" + jw.getWorkerId() + " JobData:" + + curJob.toString()); + jw.EmergencyStop(); + problemJobs.add(jw.getCurrentJob()); + it.remove(); + } // end if (diff >= + } // end if(curJob.getStopTime() + } // end if(curJob != null) + } // end While + + // Check Worker Count and add workers as necessary; + int workerCount = jobWorkerList.size(); + + int maxThreads = this.maxWorkers; + + + // no pool can go below a single thread + if (maxThreads < 1) + maxThreads = 1; + + while (workerCount != maxThreads) { + Logger.info("Resizing JobPool " + this.jobPoolID + " from " + workerCount + " to " + maxThreads); + + if (workerCount < maxThreads) { + this.startWorker(this.getNextWorkerID()); + + + if (jobWorkerList.size() <= workerCount) { + // Something didnt work correctly + Logger.warn("auditWorkers() failed to add a new JobWorker to JobPool " + this.jobPoolID + ". Worker count " + workerCount + " Worker pool size " + jobWorkerList.size() + " Aborting Audit."); + return; + } + + } else if (workerCount > maxThreads) { + synchronized (this.jobWorkerList) { + Logger.warn("Reducing workers in JobPool " + this.jobPoolID + " Worker Count: " + workerCount + " to Max threads: " + maxThreads); + // pick a worker off the list and shut it down + + JobWorker toRemove = null; + int loopTries = 5; + do { + //Infinite loop could be bad.. + toRemove = jobWorkerQueue.poll(); + } while (toRemove == null && --loopTries >= 0); + + //remove it from the list + toRemove.shutdown(); + jobWorkerList.remove(toRemove); + } + } + + // update value for next loop pass + workerCount = jobWorkerList.size(); + } + + } + + public void shutdown() { + synchronized (this.jobWorkerList) { + for (JobWorker jw : this.jobWorkerList) + jw.shutdown(); + } + } + + public void emergencyStop() { + synchronized (this.jobWorkerList) { + for (JobWorker jw : this.jobWorkerList) + jw.EmergencyStop(); + } + } + + public String getRunningQueueByClassAsString() { + return this.getQueueByClassAsString(this.jobRunList); + } + + public String getWaitQueueByClassAsString() { + return this.getQueueByClassAsString(this.jobWaitQueue); + } + + + // used by devcmds + public String setMaxWorkers(int maxWorkers) { + + if (maxWorkers > 0 && maxWorkers < 101) { + this.maxWorkers = maxWorkers; + // audit workers reduces the cap + this.auditWorkers(); + return "Max workers set to " + maxWorkers + " for JobPool_" + this.jobPoolID; + } else { + return "Max workers not set, value must be from 1-100"; + } + + } +} diff --git a/src/engine/job/JobScheduler.java b/src/engine/job/JobScheduler.java new file mode 100644 index 00000000..11699764 --- /dev/null +++ b/src/engine/job/JobScheduler.java @@ -0,0 +1,169 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + +import engine.server.MBServerStatics; + +import java.util.PriorityQueue; + + +public class JobScheduler { + + private static final JobScheduler INSTANCE = new JobScheduler(); + + private final PriorityQueue jobs; + private volatile boolean alive; + private long timeOfKill = -1; + + private JobScheduler() { + jobs = new PriorityQueue<>(MBServerStatics.SCHEDULER_INITIAL_CAPACITY); + Runnable worker = new Runnable() { + @Override + public void run() { + execution(); + } + }; + + alive = true; + + Thread t = new Thread(worker, "JobScheduler"); + t.start(); + } + + public static JobScheduler getInstance() { + return INSTANCE; + } + + /** + * This function schedules a job to execute in timeToExecution + * milliseconds from now. + * + * @param job + * @param timeToExecution + * @return + */ + public JobContainer scheduleJob(AbstractJob job, int timeToExecution) { + long timeOfExecution = System.currentTimeMillis() + timeToExecution; + JobContainer container = new JobContainer(job, timeOfExecution); + + synchronized (jobs) { + jobs.offer(container); + jobs.notify(); + } + + return container; + } + + /** + * This function schedules a job to execute at the absolute time of + * timeOfExecution (milliseconds). + * + * @param job + * @param timeOfExecution + * @return + */ + public JobContainer scheduleJob(AbstractJob job, long timeOfExecution) { + JobContainer container = new JobContainer(job, timeOfExecution); + + synchronized (jobs) { + jobs.offer(container); + jobs.notify(); + } + + return container; + } + + public boolean cancelScheduledJob(JobContainer container) { + return cancelScheduledJob(container.getJob()); + } + + public boolean cancelScheduledJob(AbstractJob job) { + JobContainer container = new JobContainer(job, -1); + + boolean success = false; + synchronized (jobs) { + success = jobs.remove(container); + jobs.notify(); + } + + return success; + } + + /** + * Stops the jobScheduler + */ + public void shutdown() { + if (alive) { + alive = false; + timeOfKill = System.currentTimeMillis(); + synchronized (jobs) { + jobs.notify(); + } + } + } + + public JobContainer pollNextJobContainer() { + if (alive) { + throw new IllegalStateException("Can't poll jobs from a live scheduler."); + } + + synchronized (jobs) { + return jobs.poll(); + } + } + + public long getTimeOfKill() { + return this.timeOfKill; + } + + public boolean isAlive() { + return this.alive; + } + + private void execution() { + long duration; + JobContainer container; + int compensation = MBServerStatics.SCHEDULER_EXECUTION_TIME_COMPENSATION; + + while (alive) { + synchronized (jobs) { + container = jobs.peek(); + if (container == null) { + // queue is empty, wait until notified (which happens after + // a new job is offered) + try { + jobs.wait(0); + } catch (InterruptedException ie) { + // do nothing + } + } else { + duration = container.timeOfExecution - System.currentTimeMillis(); + if (duration < compensation) { + jobs.poll(); + } else { + // enforce new loop + container = null; + + // sleep until the head job execution time + try { + jobs.wait(duration); + } catch (InterruptedException ie) { + // do nothing + } + } + } + } + + if (container != null) { + JobManager.getInstance().submitJob(container.job); + } + } + } +} diff --git a/src/engine/job/JobWorker.java b/src/engine/job/JobWorker.java new file mode 100644 index 00000000..e6a13ca9 --- /dev/null +++ b/src/engine/job/JobWorker.java @@ -0,0 +1,109 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.job; + +import engine.core.ControlledRunnable; +import org.pmw.tinylog.Logger; + +import java.util.Queue; + + +public class JobWorker extends ControlledRunnable { + private final int workerId; + + private final Queue jobWaitQueue; + private final Queue jobWorkerList; + + private AbstractJob currentJob; + + public JobWorker(final int workerID, int priorityQueue, + Queue jobWaitQueue, + Queue jobWorkerList) { + super("JobWorker_" + priorityQueue + '_' + workerID); + + workerId = workerID; + this.jobWaitQueue = jobWaitQueue; + this.jobWorkerList = jobWorkerList; + } + + @Override + protected boolean _Run() { + + while (this.runCmd) { + // Access to Queue is synchronized internal to JobManager + this.currentJob = this.jobWaitQueue.poll(); + + if (this.currentJob == null) { + try { + // use self as MUTEX + synchronized (this) { + this.jobWorkerList.add(this); + this.wait(); + } + + } catch (InterruptedException e) { + Logger.error(this.getThreadName(), e.getClass() + .getSimpleName() + + ": " + e.getMessage()); + break; + + } + } else { + + // execute the new job.. + this.currentJob.executeJob(this.getThreadName()); + this.currentJob = null; + } + + } + return true; + } + + @Override + protected boolean _postRun() { + return true; + } + + @Override + protected boolean _preRun() { + return true; + } + + @Override + protected void _shutdown() { + } + + @Override + protected void _startup() { + //this.logDirectINFO(this.getThreadName(), "Starting up..."); + } + + public final int getWorkerId() { + return workerId; + } + + public final AbstractJob getCurrentJob() { + return currentJob; + } + + public final boolean hasCurrentJob() { + return (currentJob != null); + } + + protected void EmergencyStop() { + this.runCmd = false; + String out = "Stack Trace"; + for (StackTraceElement e : this.getThisThread().getStackTrace()) { + out += " -> " + e.toString(); + } + Logger.info(out); + this.getThisThread().interrupt(); + } +} diff --git a/src/engine/jobs/AbstractEffectJob.java b/src/engine/jobs/AbstractEffectJob.java new file mode 100644 index 00000000..4197dcfb --- /dev/null +++ b/src/engine/jobs/AbstractEffectJob.java @@ -0,0 +1,154 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + + +public abstract class AbstractEffectJob extends AbstractScheduleJob { + + protected String stackType; + protected AbstractWorldObject target; + protected AbstractWorldObject source; + protected int trains; + protected ActionsBase action; + protected PowersBase power; + protected EffectsBase eb; + protected boolean skipSendEffect = false; + protected boolean skipApplyEffect = false; + protected boolean isChant = false; + protected boolean skipCancelEffect = false; + private boolean noOverwrite; + private int effectSourceType = 0; + private int effectSourceID = 0; + + public AbstractEffectJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb) { + super(); + this.source = source; + this.target = target; + this.stackType = stackType; + this.trains = trains; + this.action = action; + this.power = power; + this.eb = eb; + } + + @Override + protected abstract void doJob(); + + @Override + protected abstract void _cancelJob(); + + public String getStackType() { + return this.stackType; + } + + public AbstractWorldObject getTarget() { + return this.target; + } + + public AbstractWorldObject getSource() { + return this.target; + } + + public int getTrains() { + return this.trains; + } + + public ActionsBase getAction() { + return this.action; + } + + public PowersBase getPower() { + return this.power; + } + + public int getPowerToken() { + if (this.power == null) + return 0; + return this.power.getToken(); + } + + public EffectsBase getEffect() { + return this.eb; + } + + public boolean skipSendEffect() { + return this.skipSendEffect; + } + + public void setSkipSendEffect(boolean value) { + this.skipSendEffect = value; + } + + public boolean skipApplyEffect() { + return this.skipApplyEffect; + } + + public void setSkipApplyEffect(boolean value) { + this.skipApplyEffect = value; + } + + public boolean skipCancelEffect() { + return this.skipCancelEffect; + } + + public void setSkipCancelEffect(boolean value) { + this.skipCancelEffect = value; + } + + public boolean isChant() { + return this.isChant; + } + + public void setChant(boolean value) { + this.isChant = value; + } + + public void endEffect() { + if (this.eb == null) + return; + this.eb.endEffect(this.source, this.target, this.trains, this.power, this); + } + + public void endEffectNoPower() { + if (this.eb == null) + return; + this.eb.endEffectNoPower(this.trains, this); + } + + public boolean isNoOverwrite() { + return noOverwrite; + } + + public void setNoOverwrite(boolean noOverwrite) { + this.noOverwrite = noOverwrite; + } + + public int getEffectSourceType() { + return effectSourceType; + } + + public void setEffectSourceType(int effectSourceType) { + this.effectSourceType = effectSourceType; + } + + public int getEffectSourceID() { + return effectSourceID; + } + + public void setEffectSourceID(int effectSourceID) { + this.effectSourceID = effectSourceID; + } +} diff --git a/src/engine/jobs/ActivateBaneJob.java b/src/engine/jobs/ActivateBaneJob.java new file mode 100644 index 00000000..65d3cd94 --- /dev/null +++ b/src/engine/jobs/ActivateBaneJob.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum; +import engine.Enum.ChatChannelType; +import engine.gameManager.DbManager; +import engine.job.AbstractScheduleJob; +import engine.net.DispatchMessage; +import engine.net.client.msg.chat.ChatSystemMsg; +import engine.objects.City; +import org.pmw.tinylog.Logger; + +public class ActivateBaneJob extends AbstractScheduleJob { + + private final int cityUUID; + + public ActivateBaneJob(int cityUUID) { + super(); + this.cityUUID = cityUUID; + + } + + @Override + protected void doJob() { + + City city; + + city = (City) DbManager.getObject(Enum.GameObjectType.City, cityUUID); + + if (city == null) + return; + + + if (city.getBane() == null) { + Logger.info("No bane found for " + city.getCityName()); + return; + } + + if (city.getBane().isErrant()) { + Logger.info("Removed errant bane on " + city.getCityName()); + city.getBane().remove(); + return; + } + + if (city.getBane() == null) + return; + + if (city.protectionEnforced == true) + city.protectionEnforced = false; + else { + Logger.info("Bane on " + city.getCityName() + " activated for unprotected city?"); + return; + } + + Logger.info("ActivateBaneJob", "Bane on " + city.getCityName() + " is now active"); + + ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel] The Banecircle placed by " + city.getBane().getOwner().getGuild().getName() + " is now active! Buildings are now vulnerable to damage!"); + msg.setMessageType(4); // Error message + msg.setChannel(ChatChannelType.SYSTEM.getChannelID()); + + DispatchMessage.dispatchMsgToAll(msg); + } + + @Override + protected void _cancelJob() { + } + +} diff --git a/src/engine/jobs/AttackJob.java b/src/engine/jobs/AttackJob.java new file mode 100644 index 00000000..affed880 --- /dev/null +++ b/src/engine/jobs/AttackJob.java @@ -0,0 +1,40 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.CombatManager; +import engine.job.AbstractJob; +import engine.objects.AbstractCharacter; + +public class AttackJob extends AbstractJob { + + private final AbstractCharacter source; + private final int slot; + private final boolean success; + + public AttackJob(AbstractCharacter source, int slot, boolean success) { + super(); + this.source = source; + this.slot = slot; + this.success = success; + } + + @Override + protected void doJob() { + CombatManager.doCombat(this.source, slot); + } + + public boolean success() { + return this.success; + } + + protected void _cancelJob() { + } +} \ No newline at end of file diff --git a/src/engine/jobs/BaneDefaultTimeJob.java b/src/engine/jobs/BaneDefaultTimeJob.java new file mode 100644 index 00000000..54f8e162 --- /dev/null +++ b/src/engine/jobs/BaneDefaultTimeJob.java @@ -0,0 +1,47 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.objects.Bane; +import org.joda.time.DateTime; + +public class BaneDefaultTimeJob extends AbstractScheduleJob { + + private final Bane bane; + + public BaneDefaultTimeJob(Bane bane) { + super(); + this.bane = bane; + + } + + @Override + protected void doJob() { + + //bane already set. + if (this.bane.getLiveDate() != null) { + return; + } + + DateTime defaultTime = new DateTime(this.bane.getPlacementDate()); + defaultTime = defaultTime.plusDays(2); + defaultTime = defaultTime.hourOfDay().setCopy(22); + defaultTime = defaultTime.minuteOfHour().setCopy(0); + defaultTime = defaultTime.secondOfMinute().setCopy(0); + this.bane.setLiveDate(defaultTime); + + } + + @Override + protected void _cancelJob() { + } + +} diff --git a/src/engine/jobs/BasicScheduledJob.java b/src/engine/jobs/BasicScheduledJob.java new file mode 100644 index 00000000..5c29fe7f --- /dev/null +++ b/src/engine/jobs/BasicScheduledJob.java @@ -0,0 +1,114 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractJob; +import engine.job.JobScheduler; +import org.pmw.tinylog.Logger; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + + +/** + * A generic execution job which is an extension of {@link AbstractJob}. + *

+ * Intended to be used with the {@link JobScheduler}, a BasicScheduledJob will + * hold a reference to an execution method. + * + * @author Burfo + **/ + +public class BasicScheduledJob extends AbstractJob { + + private Method execution; + private Object referenceObject; + + /** + * Generates a new BasicScheduledJob that executes a static method that has + * no parameters. + * + * @param methodName Name of the static method to execute, such as "myMethod" + * @param methodClass The class in which {@code methodName} exists + */ + public BasicScheduledJob(String methodName, Class methodClass) { + this(methodName, methodClass, null); + } + + /** + * Generates a new BasicScheduledJob that executes an instance method that + * has no parameters. + * + * @param methodName Name of the instance method to execute, such as "myMethod" + * @param methodClass The class in which {@code methodName} exists + * @param referenceObject Instance of {@code methodClass} against which {@code + * methodName} should be executed + */ + @SuppressWarnings("unchecked") + public BasicScheduledJob(String methodName, Class methodClass, Object referenceObject) { + super(); + Method method = null; + try { + method = methodClass.getMethod(methodName); + } catch (SecurityException e) { + Logger.error(e); + } catch (NoSuchMethodException e) { + Logger.error(e); + } + setData(method, null); + } + + /** + * Generates a new BasicScheduledJob that executes a static method that has + * no parameters. + * + * @param executionMethod Reference to the static method to execute + */ + public BasicScheduledJob(Method executionMethod) { + this(executionMethod, null); + } + + /** + * Generates a new BasicScheduledJob that executes an instance method that + * has no parameters. + * + * @param executionMethod Reference to the static method to execute + * @param referenceObject Instanciated object against which {@code executionMethod} + * should be executed + */ + public BasicScheduledJob(Method executionMethod, Object referenceObject) { + super(); + setData(executionMethod, referenceObject); + } + + private void setData(Method executionMethod, Object referenceObject) { + this.execution = executionMethod; + this.referenceObject = referenceObject; + if (execution == null) { + Logger.error("BasicScheduledJob instanciated with no execution method."); + } + } + + @Override + protected void doJob() { + if (execution == null) { + Logger.error("BasicScheduledJob executed with nothing to execute."); + return; + } + + try { + execution.invoke(referenceObject); + } catch (IllegalArgumentException | IllegalAccessException e) { + Logger.error("BasicScheduledJob execution failed. Method: " + execution.toString(), e); + } catch (InvocationTargetException e) { + Logger.error("BasicScheduledJob execution failed. " + "Method: " + execution.toString(), e); + } + } +} diff --git a/src/engine/jobs/BonusCalcJob.java b/src/engine/jobs/BonusCalcJob.java new file mode 100644 index 00000000..0aa3e514 --- /dev/null +++ b/src/engine/jobs/BonusCalcJob.java @@ -0,0 +1,31 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractJob; +import engine.objects.AbstractCharacter; + +public class BonusCalcJob extends AbstractJob { + + private final AbstractCharacter ac; + + public BonusCalcJob(AbstractCharacter ac) { + super(); + this.ac = ac; + } + + @Override + protected void doJob() { + if (this.ac != null) { + this.ac.applyBonuses(); + + } + } +} diff --git a/src/engine/jobs/CSessionCleanupJob.java b/src/engine/jobs/CSessionCleanupJob.java new file mode 100644 index 00000000..38002471 --- /dev/null +++ b/src/engine/jobs/CSessionCleanupJob.java @@ -0,0 +1,28 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.SessionManager; +import engine.job.AbstractJob; + +public class CSessionCleanupJob extends AbstractJob { + + private final String secKey; + + public CSessionCleanupJob(String key) { + super(); + this.secKey = key; + } + + @Override + protected void doJob() { + SessionManager.cSessionCleanup(secKey); + } +} diff --git a/src/engine/jobs/ChangeAltitudeJob.java b/src/engine/jobs/ChangeAltitudeJob.java new file mode 100644 index 00000000..72a1cd8c --- /dev/null +++ b/src/engine/jobs/ChangeAltitudeJob.java @@ -0,0 +1,42 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.MovementManager; +import engine.job.AbstractScheduleJob; +import engine.objects.AbstractCharacter; + +public class ChangeAltitudeJob extends AbstractScheduleJob { + + private final AbstractCharacter ac; + private final float targetAlt; + private final float startAlt; + + public ChangeAltitudeJob(AbstractCharacter ac, float startAlt, float targetAlt) { + super(); + this.ac = ac; + this.startAlt = startAlt; + this.targetAlt = targetAlt; + } + + @Override + protected void doJob() { + if (this.ac != null) + MovementManager.finishChangeAltitude(ac, targetAlt); + } + + @Override + protected void _cancelJob() { + } + + public float getStartAlt() { + return startAlt; + } +} diff --git a/src/engine/jobs/ChantJob.java b/src/engine/jobs/ChantJob.java new file mode 100644 index 00000000..cfb2bfb7 --- /dev/null +++ b/src/engine/jobs/ChantJob.java @@ -0,0 +1,102 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum.GameObjectType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.PowersManager; +import engine.gameManager.SessionManager; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerBonuses; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.util.HashSet; + + +public class ChantJob extends AbstractEffectJob { + + private final AbstractEffectJob aej; + private int iteration = 0; + + public ChantJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, AbstractEffectJob aej) { + super(source, target, stackType, trains, action, power, eb); + this.aej = aej; + } + + @Override + protected void doJob() { + if (this.aej == null || this.source == null || this.target == null || this.action == null || this.power == null || this.source == null || this.eb == null) + return; + PlayerBonuses bonuses = null; + + //if player isnt in game, do not run chant. + if (this.source.getObjectType().equals(GameObjectType.PlayerCharacter)) { + if (SessionManager.getPlayerCharacterByID(this.source.getObjectUUID()) == null) + return; + } + if (AbstractWorldObject.IsAbstractCharacter(source)) + bonuses = ((AbstractCharacter) source).getBonuses(); + if (!this.source.isAlive()) { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + if (AbstractWorldObject.IsAbstractCharacter(source)) + ((AbstractCharacter) source).cancelLastChant(); + } else if (bonuses != null && bonuses.getBool(ModType.Silenced, SourceType.None)) { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + if (AbstractWorldObject.IsAbstractCharacter(source)) + ((AbstractCharacter) source).cancelLastChant(); + } else if (AbstractWorldObject.IsAbstractCharacter(source) && ((AbstractCharacter) source).isSit()) { + return; + } else if (this.iteration < this.power.getChantIterations() && AbstractWorldObject.IsAbstractCharacter(source)) { + this.skipSendEffect = true; + this.iteration++; + + // *** Refactor holy wtf batman + + String stackType = action.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(action.getUUID()) : stackType; + + HashSet awolist = null; + if (this.source instanceof PlayerCharacter) + awolist = PowersManager.getAllTargets(this.source, this.source.getLoc(), (PlayerCharacter) this.source, this.power); + else + awolist = new HashSet<>(); + for (AbstractWorldObject awo : awolist) { + + if (awo == null) + continue; + + PowersManager.finishApplyPowerA((AbstractCharacter) this.source, awo, awo.getLoc(), this.power, this.trains, true); + + } + + if (AbstractWorldObject.IsAbstractCharacter(source)) + //handle invul + if (power.getUUID() != 334) + ((AbstractCharacter) this.source).setLastChant((int) (this.power.getChantDuration()) * 1000, this); + else + ((AbstractCharacter) this.source).setLastChant((int) (this.power.getChantDuration()) * 1000, this); + } else { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + if (AbstractWorldObject.IsAbstractCharacter(source)) { + ((AbstractCharacter) source).cancelLastChant(); + } + } + } + + @Override + protected void _cancelJob() { + } + +} diff --git a/src/engine/jobs/CloseGateJob.java b/src/engine/jobs/CloseGateJob.java new file mode 100644 index 00000000..46168b76 --- /dev/null +++ b/src/engine/jobs/CloseGateJob.java @@ -0,0 +1,46 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum; +import engine.Enum.PortalType; +import engine.job.AbstractScheduleJob; +import engine.objects.Building; +import engine.objects.Runegate; +import org.pmw.tinylog.Logger; + +public class CloseGateJob extends AbstractScheduleJob { + + private final Building building; + private final Enum.PortalType portalType; + + public CloseGateJob(Building building, PortalType portalType) { + + super(); + this.building = building; + this.portalType = portalType; + } + + @Override + protected void doJob() { + + if (building == null) { + Logger.error("Rungate building was null"); + return; + } + + Runegate._runegates.get(building.getObjectUUID()).deactivatePortal(portalType); + } + + @Override + protected void _cancelJob() { + } +} + diff --git a/src/engine/jobs/DamageOverTimeJob.java b/src/engine/jobs/DamageOverTimeJob.java new file mode 100644 index 00000000..eaa4d25f --- /dev/null +++ b/src/engine/jobs/DamageOverTimeJob.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum.GameObjectType; +import engine.gameManager.PowersManager; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import engine.powers.poweractions.DamageOverTimePowerAction; + + +public class DamageOverTimeJob extends AbstractEffectJob { + + private final DamageOverTimePowerAction dot; + private int iteration = 0; + private int liveCounter = 0; + + public DamageOverTimeJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, DamageOverTimePowerAction dot) { + super(source, target, stackType, trains, action, power, eb); + + this.dot = dot; + if (this.target != null && AbstractWorldObject.IsAbstractCharacter(target)) + this.liveCounter = ((AbstractCharacter) target).getLiveCounter(); + + this.iteration = action.getDurationInSeconds(trains) / this.dot.getNumIterations(); + } + + @Override + protected void doJob() { + if (this.target.getObjectType().equals(GameObjectType.Building) + && ((Building) this.target).isVulnerable() == false) { + _cancelJob(); + return; + } + + + if (this.dot == null || this.target == null || this.action == null || this.source == null || this.eb == null) + return; + if (AbstractWorldObject.IsAbstractCharacter(target) && ((AbstractCharacter) this.target).getLiveCounter() != liveCounter) { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + return; + } + if (!this.target.isAlive()) { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + return; + } + + this.iteration--; + + if (this.iteration < 0) { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + return; + } + this.skipSendEffect = true; + String stackType = action.getStackType(); + if (stackType.equals("IgnoreStack")) + this.target.addEffect(Integer.toString(action.getUUID()), getTickLength(), this, this.eb, this.trains); + else + this.target.addEffect(stackType, getTickLength(), this, this.eb, this.trains); + if (AbstractWorldObject.IsAbstractCharacter(source)) + eb.startEffect((AbstractCharacter) this.source, this.target, this.trains, this); + } + + @Override + protected void _cancelJob() { + PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this); + } + + public int getIteration() { + return this.iteration; + } + + public int getTickLength() { + return this.dot.getNumIterations() * 1000; + } + + public int inc() { + this.iteration++; + return this.iteration; + } +} diff --git a/src/engine/jobs/DatabaseUpdateJob.java b/src/engine/jobs/DatabaseUpdateJob.java new file mode 100644 index 00000000..fa45cf44 --- /dev/null +++ b/src/engine/jobs/DatabaseUpdateJob.java @@ -0,0 +1,64 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.job.AbstractScheduleJob; +import engine.objects.AbstractGameObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +public class DatabaseUpdateJob extends AbstractScheduleJob { + + private final AbstractGameObject ago; + private final String type; + + public DatabaseUpdateJob(AbstractGameObject ago, String type) { + super(); + this.ago = ago; + this.type = type; + } + + @Override + protected void doJob() { + if (this.ago == null) + return; + ago.removeDatabaseJob(this.type, false); + + if (ago.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + PlayerCharacter pc = (PlayerCharacter) ago; + + switch (this.type) { + case "Skills": + pc.updateSkillsAndPowersToDatabase(); + break; + case "Stats": + DbManager.PlayerCharacterQueries.UPDATE_CHARACTER_STATS(pc); + break; + case "EXP": + DbManager.PlayerCharacterQueries.UPDATE_CHARACTER_EXPERIENCE(pc); + break; + } + + } else if (ago instanceof Building) { + Building b = (Building) ago; + if (this.type.equals("health")) + DbManager.BuildingQueries.UPDATE_BUILDING_HEALTH(b.getObjectUUID(), (int) (b.getHealth())); + } + + } + + @Override + protected void _cancelJob() { + } +} + diff --git a/src/engine/jobs/DebugTimerJob.java b/src/engine/jobs/DebugTimerJob.java new file mode 100644 index 00000000..17f7ec04 --- /dev/null +++ b/src/engine/jobs/DebugTimerJob.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.ChatManager; +import engine.job.AbstractScheduleJob; +import engine.objects.PlayerCharacter; + +public class DebugTimerJob extends AbstractScheduleJob { + + private final PlayerCharacter pc; + private final String command; + private final int commandNum; + private final int duration; + + public DebugTimerJob(PlayerCharacter pc, String command, int commandNum, int duration) { + super(); + this.pc = pc; + this.command = command; + this.commandNum = commandNum; + this.duration = duration; + } + + @Override + protected void doJob() { + if (this.pc == null) { + return; + } + + String text; + switch (this.commandNum) { + case 1: //health + text = "Health: " + pc.getHealth(); + ChatManager.chatSystemInfo(pc, text); + break; + case 2: //mana + text = "Mana: " + pc.getMana(); + ChatManager.chatSystemInfo(pc, text); + break; + case 3: //stamina + text = "Stamina: " + pc.getStamina(); + ChatManager.chatSystemInfo(pc, text); + break; + default: + } + + //re-up the timer for this + this.pc.renewTimer(command, this, duration); + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/DeferredPowerJob.java b/src/engine/jobs/DeferredPowerJob.java new file mode 100644 index 00000000..6cd6973f --- /dev/null +++ b/src/engine/jobs/DeferredPowerJob.java @@ -0,0 +1,107 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.CombatManager; +import engine.gameManager.PowersManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import engine.powers.poweractions.ApplyEffectPowerAction; +import engine.powers.poweractions.DeferredPowerPowerAction; + +public class DeferredPowerJob extends AbstractEffectJob { + + private final DeferredPowerPowerAction def; + + public DeferredPowerJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, DeferredPowerPowerAction def) { + super(source, target, stackType, trains, action, power, eb); + this.def = def; + } + + public DeferredPowerJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, ApplyEffectPowerAction def) { + super(source, target, stackType, trains, action, power, eb); + this.def = null; + } + + @Override + protected void doJob() { + //Power ended with no attack, cancel weapon power boost + if (this.source != null && this.source instanceof PlayerCharacter) { + ((PlayerCharacter) this.source).setWeaponPower(null); + } + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + } + + @Override + protected void _cancelJob() { + //Attack happened. + PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this); + } + + public void attack(AbstractWorldObject tar, float attackRange) { + + if (this.source == null) + return; + + if (!AbstractWorldObject.IsAbstractCharacter(tar)) + return; + + if (this.power == null) + return; + + + switch (this.source.getObjectType()) { + + case PlayerCharacter: + + if (def == null) { + //No powers applied, just reset weapon power. + ((PlayerCharacter) this.source).setWeaponPower(null); + return; + } + float powerRange = this.power.getWeaponRange(); + + // Wtf? Method returns TRUE if rage test fails? Seriously? + + //DO valid range check ONLY for weapon powers with range less than attack range. + if (attackRange > powerRange) + if (CombatManager.NotInRange((AbstractCharacter) this.source, tar, powerRange)) + return; + + //Range check passed, apply power and clear weapon power. + ((PlayerCharacter) this.source).setWeaponPower(null); + + + //weapon powers with no deferedpoweraction have null Def, but still have bonuses applied already and will finish here. + + + PowersManager.applyPower((AbstractCharacter) this.source, tar, Vector3fImmutable.ZERO, def.getDeferredPowerID(), this.trains, false); + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + break; + case Mob: + ((Mob) this.source).setWeaponPower(null); + if (def == null) { + return; + } + + PowersManager.applyPower((AbstractCharacter) this.source, tar, Vector3fImmutable.ZERO, def.getDeferredPowerID(), this.trains, false); + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + break; + + } + + } +} diff --git a/src/engine/jobs/DisconnectJob.java b/src/engine/jobs/DisconnectJob.java new file mode 100644 index 00000000..f78448cc --- /dev/null +++ b/src/engine/jobs/DisconnectJob.java @@ -0,0 +1,34 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.net.client.ClientConnection; + +public class DisconnectJob extends AbstractScheduleJob { + + private final ClientConnection origin; + + public DisconnectJob(ClientConnection origin) { + super(); + this.origin = origin; + } + + @Override + protected void doJob() { + if (this.origin != null) { + this.origin.disconnect(); + } + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/DoorCloseJob.java b/src/engine/jobs/DoorCloseJob.java new file mode 100644 index 00000000..8606d6f3 --- /dev/null +++ b/src/engine/jobs/DoorCloseJob.java @@ -0,0 +1,50 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum.DoorState; +import engine.job.AbstractScheduleJob; +import engine.net.DispatchMessage; +import engine.net.client.msg.DoorTryOpenMsg; +import engine.objects.Blueprint; +import engine.objects.Building; + +public class DoorCloseJob extends AbstractScheduleJob { + + Building building; + int door; + + public DoorCloseJob(Building building, int door) { + super(); + this.building = building; + this.door = door; + } + + @Override + protected void doJob() { + + int doorNumber; + + if (this.building == null) + return; + + doorNumber = Blueprint.getDoorNumberbyMesh(this.door); + + this.building.setDoorState(doorNumber, DoorState.CLOSED); + + DoorTryOpenMsg msg = new DoorTryOpenMsg(door, this.building.getObjectUUID(), 0, (byte) 0); + DispatchMessage.sendToAllInRange(building, msg); + + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/EndFearJob.java b/src/engine/jobs/EndFearJob.java new file mode 100644 index 00000000..fef762d5 --- /dev/null +++ b/src/engine/jobs/EndFearJob.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +public class EndFearJob extends AbstractEffectJob { + + public EndFearJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb) { + super(source, target, stackType, trains, action, power, eb); + } + + @Override + protected void doJob() { + + //cancel fear for mob. + + if (this.target == null || (!(this.target instanceof Mob))) + return; + + ((Mob) this.target).setFearedObject(null); + } + + @Override + protected void _cancelJob() { + + //cancel fear for mob. + + if (this.target == null || (!(this.target instanceof Mob))) + return; + + ((Mob) this.target).setFearedObject(null); + } +} diff --git a/src/engine/jobs/FinishCooldownTimeJob.java b/src/engine/jobs/FinishCooldownTimeJob.java new file mode 100644 index 00000000..6759cf87 --- /dev/null +++ b/src/engine/jobs/FinishCooldownTimeJob.java @@ -0,0 +1,32 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.PowersManager; +import engine.job.AbstractJob; +import engine.net.client.msg.PerformActionMsg; +import engine.objects.PlayerCharacter; + +public class FinishCooldownTimeJob extends AbstractJob { + + PlayerCharacter pc; + PerformActionMsg msg; + + public FinishCooldownTimeJob(PlayerCharacter pc, PerformActionMsg msg) { + super(); + this.pc = pc; + this.msg = msg; + } + + @Override + protected void doJob() { + PowersManager.finishCooldownTime(this.msg, this.pc); + } +} diff --git a/src/engine/jobs/FinishEffectTimeJob.java b/src/engine/jobs/FinishEffectTimeJob.java new file mode 100644 index 00000000..60786f68 --- /dev/null +++ b/src/engine/jobs/FinishEffectTimeJob.java @@ -0,0 +1,33 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.PowersManager; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +public class FinishEffectTimeJob extends AbstractEffectJob { + + public FinishEffectTimeJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb) { + super(source, target, stackType, trains, action, power, eb); + } + + @Override + protected void doJob() { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + } + + @Override + protected void _cancelJob() { + PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this); + } +} diff --git a/src/engine/jobs/FinishRecycleTimeJob.java b/src/engine/jobs/FinishRecycleTimeJob.java new file mode 100644 index 00000000..47c4126a --- /dev/null +++ b/src/engine/jobs/FinishRecycleTimeJob.java @@ -0,0 +1,36 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.PowersManager; +import engine.job.AbstractScheduleJob; +import engine.net.client.msg.PerformActionMsg; +import engine.objects.PlayerCharacter; + +public class FinishRecycleTimeJob extends AbstractScheduleJob { + + PlayerCharacter pc; + PerformActionMsg msg; + + public FinishRecycleTimeJob(PlayerCharacter pc, PerformActionMsg msg) { + super(); + this.pc = pc; + this.msg = msg; + } + + @Override + protected void doJob() { + PowersManager.finishRecycleTime(this.msg, this.pc, false); + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/FinishSpireEffectJob.java b/src/engine/jobs/FinishSpireEffectJob.java new file mode 100644 index 00000000..d4b895e1 --- /dev/null +++ b/src/engine/jobs/FinishSpireEffectJob.java @@ -0,0 +1,37 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerCharacter; +import engine.powers.EffectsBase; + +public class FinishSpireEffectJob extends AbstractEffectJob { + + public FinishSpireEffectJob(AbstractWorldObject target, String stackType, EffectsBase eb, int trains) { + super(null, target, stackType, trains, null, null, eb); + } + + @Override + protected void doJob() { + + PlayerCharacter pc = (PlayerCharacter) target; + + if (pc == null) + return; + + pc.endEffectNoPower(Integer.toString(eb.getUUID())); + + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/FinishSummonsJob.java b/src/engine/jobs/FinishSummonsJob.java new file mode 100644 index 00000000..0ee0fc68 --- /dev/null +++ b/src/engine/jobs/FinishSummonsJob.java @@ -0,0 +1,81 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.PowersManager; +import engine.job.AbstractScheduleJob; +import engine.job.JobContainer; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.PlayerCharacter; + +import java.util.concurrent.ConcurrentHashMap; + +public class FinishSummonsJob extends AbstractScheduleJob { + + PlayerCharacter source; + PlayerCharacter target; + + public FinishSummonsJob(PlayerCharacter source, PlayerCharacter target) { + super(); + this.source = source; + this.target = target; + } + + @Override + protected void doJob() { + + if (this.target == null) + return; + + //clear summon timer + + ConcurrentHashMap timers = this.target.getTimers(); + + if (timers != null && timers.containsKey("Summon")) + timers.remove("Summon"); + + if (this.source == null || !this.source.isAlive() || !this.target.isAlive()) + return; + + // cannot summon a player in combat + if (this.target.isCombat()) { + + ErrorPopupMsg.sendErrorMsg(this.source, "Cannot summon player in combat."); + + PowersManager.finishRecycleTime(428523680, this.source, false); + return; + } + + if (this.target.getBonuses() != null && this.target.getBonuses().getBool(ModType.BlockedPowerType, SourceType.SUMMON)) { + ErrorPopupMsg.sendErrorMsg(this.target, "You have been blocked from receiving summons!"); + ErrorPopupMsg.sendErrorMsg(this.source, "Target is blocked from receiving summons!"); + return; + } + + if (this.source.region != null) + this.target.setRegion(this.source.region); + //teleport target to source + target.teleport(source.getLoc()); + } + + @Override + protected void _cancelJob() { + } + + public PlayerCharacter getSource() { + return this.source; + } + + public PlayerCharacter getTarget() { + return this.target; + } +} diff --git a/src/engine/jobs/LoadEffectsJob.java b/src/engine/jobs/LoadEffectsJob.java new file mode 100644 index 00000000..4ad1ecb8 --- /dev/null +++ b/src/engine/jobs/LoadEffectsJob.java @@ -0,0 +1,48 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractJob; +import engine.net.client.ClientConnection; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; + +import java.util.ArrayList; + +public class LoadEffectsJob extends AbstractJob { + + ArrayList acsToLoad; + ClientConnection originToSend; + + public LoadEffectsJob(ArrayList acsToLoad, ClientConnection origin) { + this.acsToLoad = acsToLoad; + this.originToSend = origin; + + } + + @Override + protected void doJob() { + if (this.originToSend == null) { + return; + } + + for (AbstractWorldObject awo : this.acsToLoad) { + + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter acToLoad = (AbstractCharacter) awo; + acToLoad.sendAllEffects(this.originToSend); + + } + + } + + } + +} diff --git a/src/engine/jobs/LogoutCharacterJob.java b/src/engine/jobs/LogoutCharacterJob.java new file mode 100644 index 00000000..3be22f07 --- /dev/null +++ b/src/engine/jobs/LogoutCharacterJob.java @@ -0,0 +1,37 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.objects.PlayerCharacter; +import engine.server.world.WorldServer; + +public class LogoutCharacterJob extends AbstractScheduleJob { + + private final PlayerCharacter pc; + private final WorldServer server; + + public LogoutCharacterJob(PlayerCharacter pc, WorldServer server) { + super(); + this.pc = pc; + this.server = server; + } + + @Override + protected void doJob() { + server.logoutCharacter(this.pc); + } + + @Override + protected void _cancelJob() { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/jobs/NoTimeJob.java b/src/engine/jobs/NoTimeJob.java new file mode 100644 index 00000000..309bec03 --- /dev/null +++ b/src/engine/jobs/NoTimeJob.java @@ -0,0 +1,28 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.objects.AbstractWorldObject; +import engine.powers.EffectsBase; + +public class NoTimeJob extends AbstractEffectJob { + + public NoTimeJob(AbstractWorldObject target, String stackType, EffectsBase eb, int trains) { + super(null, target, stackType, trains, null, null, eb); + } + + @Override + protected void doJob() { + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/PersistentAoeJob.java b/src/engine/jobs/PersistentAoeJob.java new file mode 100644 index 00000000..68465274 --- /dev/null +++ b/src/engine/jobs/PersistentAoeJob.java @@ -0,0 +1,109 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum.GameObjectType; +import engine.gameManager.PowersManager; +import engine.math.Vector3fImmutable; +import engine.net.client.msg.PerformActionMsg; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.util.HashSet; + +public class PersistentAoeJob extends AbstractEffectJob { + + private final AbstractEffectJob aej; + private int iteration = 0; + private Vector3fImmutable targetLoc; + private Vector3fImmutable lastLoc; + + public PersistentAoeJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, AbstractEffectJob aej, Vector3fImmutable targetLoc) { + super(source, target, stackType, trains, action, power, eb); + this.aej = aej; + if (target != null && this.target.getObjectType() == GameObjectType.PlayerCharacter) + this.targetLoc = this.target.getLoc(); + else + this.targetLoc = targetLoc; + this.lastLoc = targetLoc; + } + + @Override + protected void doJob() { + + if (this.aej == null || this.source == null || this.action == null || this.power == null || this.source == null || this.eb == null) + return; + + if (!this.source.isAlive()) + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + else if (this.iteration < this.power.getChantIterations()) { + + + this.skipSendEffect = true; + this.iteration++; + + + if (this.target != null) { + this.lastLoc = this.target.getLoc(); + this.targetLoc = this.target.getLoc(); + } + + String stackType = action.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(action.getUUID()) : stackType; + HashSet awolist = null; + + + if (this.source instanceof PlayerCharacter) + awolist = PowersManager.getAllTargets(null, this.targetLoc, (PlayerCharacter) this.source, this.power); + else + awolist = new HashSet<>(); + PerformActionMsg msg = new PerformActionMsg(power.getToken(), 9999, source + .getObjectType().ordinal(), source.getObjectUUID(), source.getObjectType().ordinal(), + source.getObjectUUID(), 0, 0, 0, 2, 0); + + + for (AbstractWorldObject awo : awolist) { + + //judge the defense of the target + + + if (awo == null + || PowersManager.testAttack((PlayerCharacter) this.source, awo, this.power, msg)) + continue; + + PowersManager.finishApplyPowerA((AbstractCharacter) this.source, awo, this.targetLoc, this.power, this.trains, true); + if (this.target != null && !this.target.isAlive()) { + this.target = null; + } + + } + if (AbstractWorldObject.IsAbstractCharacter(source)) + ((AbstractCharacter) this.source).addPersistantAoe(stackType, (int) (this.power.getChantDuration() * 1000), this, this.eb, this.trains); + } else + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + } + + @Override + protected void _cancelJob() { + } + + public int getIteration() { + return this.iteration; + } + + public int inc() { + this.iteration++; + return this.iteration; + } +} diff --git a/src/engine/jobs/RefreshGroupJob.java b/src/engine/jobs/RefreshGroupJob.java new file mode 100644 index 00000000..fcc7c9c9 --- /dev/null +++ b/src/engine/jobs/RefreshGroupJob.java @@ -0,0 +1,82 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.GroupManager; +import engine.job.AbstractJob; +import engine.net.client.ClientConnection; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +public class RefreshGroupJob extends AbstractJob { + + private final PlayerCharacter pc; + private final boolean wholeGroup; + private ClientConnection origin; + private Group grp; + private PlayerCharacter pcToRefresh; + + public RefreshGroupJob(PlayerCharacter pc, PlayerCharacter pcToRefresh) { + super(); + this.pc = pc; + if (pc != null) { + this.origin = pc.getClientConnection(); + this.grp = GroupManager.getGroup(pc); + } + this.pcToRefresh = pcToRefresh; + this.wholeGroup = false; + } + + public RefreshGroupJob(PlayerCharacter pc) { + super(); + this.pc = pc; + if (pc != null) { + this.origin = pc.getClientConnection(); + this.grp = GroupManager.getGroup(pc); + } + this.wholeGroup = true; + } + + @Override + protected void doJob() { + + if (this.pc == null || this.origin == null || grp == null) { + return; + } + + if (wholeGroup) { + + // refresh everyone in the group including me + // check that we are in the same group as when we submitted the job + + if (GroupManager.getGroup(pc) != null && GroupManager.getGroup(pc) == grp) { + + // refresh pc's group list for just the one player that needed refreshing + + GroupManager.RefreshMyGroupList(pc, origin); + GroupManager.RefreshOthersGroupList(pc); + } + + return; + } + + // only refresh the single player + if (this.pcToRefresh == null) + return; + + // check that we are in the same group as when we submitted the job + if (GroupManager.getGroup(pc) != null && GroupManager.getGroup(pc) == grp) { + // refresh pc's group list for just the one player that needed refreshing + GroupManager.RefreshMyGroupListSinglePlayer(pc, origin, pcToRefresh); + } + + } + +} diff --git a/src/engine/jobs/RemoveCorpseJob.java b/src/engine/jobs/RemoveCorpseJob.java new file mode 100644 index 00000000..78bf73bd --- /dev/null +++ b/src/engine/jobs/RemoveCorpseJob.java @@ -0,0 +1,35 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.objects.Corpse; + +public class RemoveCorpseJob extends AbstractScheduleJob { + + private final Corpse corpse; + + public RemoveCorpseJob(Corpse corpse) { + super(); + this.corpse = corpse; + } + + @Override + protected void doJob() { + + if (this.corpse != null) + Corpse.removeCorpse(corpse, true); + + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/SiegeSpireWithdrawlJob.java b/src/engine/jobs/SiegeSpireWithdrawlJob.java new file mode 100644 index 00000000..dbd270af --- /dev/null +++ b/src/engine/jobs/SiegeSpireWithdrawlJob.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.objects.Building; +import engine.objects.City; +import org.pmw.tinylog.Logger; + +public class SiegeSpireWithdrawlJob extends AbstractScheduleJob { + + private Building spire = null; + + public SiegeSpireWithdrawlJob(Building spire) { + super(); + this.spire = spire; + } + + @Override + protected void doJob() { + + if (spire == null) + return; + + // Early exit if someone disabled the spire + + if (!spire.isSpireIsActive()) + return; + + City buildingCity = spire.getCity(); + + if (buildingCity == null) + return; + + + buildingCity.transactionLock.writeLock().lock(); + try { + + // If the spire runs out of money, disable it. + //*** Refactor: 5000 every 30 seconds? Wtf? + + if (!spire.hasFunds(5000)) { + spire.disableSpire(true); + return; + } + if (spire.getStrongboxValue() < 5000) { + spire.disableSpire(true); + return; + } + + // Deduct the activation cost from the strongbox and resubmit the job + + if (!spire.transferGold(-5000, false)) + return; + JobContainer jc = JobScheduler.getInstance().scheduleJob(new SiegeSpireWithdrawlJob(spire), 300000); + spire.getTimers().put("SpireWithdrawl", jc); + + } catch (Exception e) { + Logger.error(e); + } finally { + buildingCity.transactionLock.writeLock().unlock(); + } + + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/StuckJob.java b/src/engine/jobs/StuckJob.java new file mode 100644 index 00000000..79d5541c --- /dev/null +++ b/src/engine/jobs/StuckJob.java @@ -0,0 +1,81 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.jobs; + +import engine.InterestManagement.WorldGrid; +import engine.job.AbstractScheduleJob; +import engine.math.Bounds; +import engine.math.Vector3fImmutable; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + +import java.util.HashSet; + +public class StuckJob extends AbstractScheduleJob { + + private final PlayerCharacter player; + + public StuckJob(PlayerCharacter player) { + super(); + this.player = player; + } + + @Override + protected void doJob() { + + Vector3fImmutable stuckLoc; + Building building = null; + + if (player == null) + return; + + if (player.getClientConnection() == null) + return; + + HashSet awoList = WorldGrid.getObjectsInRangePartial(player, 150, MBServerStatics.MASK_BUILDING); + + for (AbstractWorldObject awo : awoList) { + + Building buildingEntry = (Building) awo; + + if (buildingEntry.getStuckLocation() == null) + continue; + + if (Bounds.collide(player.getLoc(), buildingEntry)) { + building = buildingEntry; + break; + + } + } + + if (building == null) { + ErrorPopupMsg.sendErrorMsg(player, "Unable to find desired location"); + return; + } + + stuckLoc = building.getStuckLocation(); + + if (stuckLoc == null) { + ErrorPopupMsg.sendErrorMsg(player, "Unable to find desired location"); + return; + } + + player.teleport(stuckLoc); + + } + + @Override + protected void _cancelJob() { + } + +} + diff --git a/src/engine/jobs/SummonSendJob.java b/src/engine/jobs/SummonSendJob.java new file mode 100644 index 00000000..0e5951a4 --- /dev/null +++ b/src/engine/jobs/SummonSendJob.java @@ -0,0 +1,54 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.job.JobContainer; +import engine.objects.PlayerCharacter; + +import java.util.concurrent.ConcurrentHashMap; + +public class SummonSendJob extends AbstractScheduleJob { + + PlayerCharacter source; + PlayerCharacter target; + + public SummonSendJob(PlayerCharacter source, PlayerCharacter target) { + super(); + this.source = source; + this.target = target; + } + + @Override + protected void doJob() { + + if (this.source == null) + return; + + //clear summon send timer + ConcurrentHashMap timers = this.source.getTimers(); + + if (timers != null && timers.containsKey("SummonSend")) + timers.remove("SummonSend"); + + } + + @Override + protected void _cancelJob() { + } + + public PlayerCharacter getSource() { + return this.source; + } + + public PlayerCharacter getTarget() { + return this.target; + } +} diff --git a/src/engine/jobs/TeleportJob.java b/src/engine/jobs/TeleportJob.java new file mode 100644 index 00000000..0bb94f8e --- /dev/null +++ b/src/engine/jobs/TeleportJob.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.math.Vector3fImmutable; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.NPC; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; + +public class TeleportJob extends AbstractScheduleJob { + + private final ClientConnection origin; + private final NPC npc; + private final PlayerCharacter pc; + private final Vector3fImmutable loc; + private final boolean setSafeMode; + private int oldLiveCounter; + + public TeleportJob(PlayerCharacter pc, NPC npc, Vector3fImmutable loc, ClientConnection origin, boolean setSafeMode) { + super(); + this.pc = pc; + this.npc = npc; + this.loc = loc; + this.origin = origin; + this.setSafeMode = setSafeMode; + if (pc != null) { + this.oldLiveCounter = pc.getLiveCounter(); + } + } + + @Override + protected void doJob() { + + if (this.pc == null || this.npc == null || this.origin == null) + return; + + if (!pc.isAlive() || this.oldLiveCounter != pc.getLiveCounter()) + return; + + if (pc.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { + ErrorPopupMsg.sendErrorPopup(pc, 114); + return; + } + + pc.teleport(loc); + + if (this.setSafeMode) + pc.setSafeMode(); + + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/TrackJob.java b/src/engine/jobs/TrackJob.java new file mode 100644 index 00000000..066292b4 --- /dev/null +++ b/src/engine/jobs/TrackJob.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum; +import engine.gameManager.PowersManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.TrackArrowMsg; +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import engine.powers.poweractions.TrackPowerAction; +import engine.server.MBServerStatics; + +import static engine.math.FastMath.sqr; + +public class TrackJob extends AbstractEffectJob { + + private final TrackPowerAction tpa; + + public TrackJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, TrackPowerAction tpa) { + super(source, target, stackType, trains, action, power, eb); + this.tpa = tpa; + } + + @Override + protected void doJob() { + + if (this.tpa == null || this.target == null || this.action == null || this.source == null || this.eb == null || !(this.source instanceof PlayerCharacter)) + return; + + if (this.target.isAlive() == false) { + sendTrackArrow(Float.intBitsToFloat(0x7E967699)); + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + return; + } + + String stackType = action.getStackType(); + + float distanceSquared = this.target.getLoc().distanceSquared2D(this.source.getLoc()); + + int speed; + + if (distanceSquared < sqr(MBServerStatics.TRACK_ARROW_FAST_RANGE)) + speed = MBServerStatics.TRACK_ARROW_SENSITIVITY_FAST; + else + speed = MBServerStatics.TRACK_ARROW_SENSITIVITY; + + this.source.addEffect(stackType, speed, this, this.eb, this.trains); + + Vector3fImmutable dir = this.target.getLoc().subtract2D(this.source.getLoc()); + dir = dir.normalize(); + + sendTrackArrow(dir.getRotation()); + + } + + @Override + protected void _cancelJob() { + sendTrackArrow(Float.intBitsToFloat(0x7E967699)); + PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this); + } + + private void sendTrackArrow(float rotation) { + + if (this.source != null && this.source instanceof PlayerCharacter) { + PlayerCharacter pc = (PlayerCharacter) this.source; + + if (pc == null) + return; + + // We send track arrows over primary channel + + TrackArrowMsg tam = new TrackArrowMsg(rotation); + Dispatch dispatch = Dispatch.borrow(pc, tam); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + } + } + +} diff --git a/src/engine/jobs/TransferStatOTJob.java b/src/engine/jobs/TransferStatOTJob.java new file mode 100644 index 00000000..e4fa31ac --- /dev/null +++ b/src/engine/jobs/TransferStatOTJob.java @@ -0,0 +1,74 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.PowersManager; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import engine.powers.poweractions.TransferStatOTPowerAction; + +public class TransferStatOTJob extends AbstractEffectJob { + + private final TransferStatOTPowerAction dot; + private int iteration = 0; + + public TransferStatOTJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, TransferStatOTPowerAction dot) { + super(source, target, stackType, trains, action, power, eb); + this.dot = dot; + this.iteration = action.getDurationInSeconds(trains) / this.dot.getNumIterations(); + } + + @Override + protected void doJob() { + if (this.dot == null || this.target == null || this.action == null || this.source == null || this.eb == null || this.action == null || this.power == null) + return; + if (!this.target.isAlive()) { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + return; + } + + iteration--; + + if (iteration < 0) { + PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains); + return; + } + + this.skipSendEffect = true; + + String stackType = action.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(action.getUUID()) : stackType; + this.target.addEffect(stackType, getTickLength(), this, this.eb, this.trains); + if (AbstractWorldObject.IsAbstractCharacter(source)) + this.dot.runAction((AbstractCharacter) this.source, this.target, this.trains, this.action, this.power); + + } + + @Override + protected void _cancelJob() { + PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this); + } + + public int getIteration() { + return this.iteration; + } + + public int getTickLength() { + return this.dot.getNumIterations() * 1000; + } + + public int inc() { + this.iteration++; + return this.iteration; + } +} diff --git a/src/engine/jobs/UpdateGroupJob.java b/src/engine/jobs/UpdateGroupJob.java new file mode 100644 index 00000000..5d31f0a3 --- /dev/null +++ b/src/engine/jobs/UpdateGroupJob.java @@ -0,0 +1,51 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.jobs; + +import engine.gameManager.GroupManager; +import engine.job.AbstractScheduleJob; +import engine.job.JobScheduler; +import engine.objects.Group; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +public class UpdateGroupJob extends AbstractScheduleJob { + + private final Group group; + + public UpdateGroupJob(Group group) { + super(); + this.group = group; + } + + @Override + protected void doJob() { + + if (this.group == null) + return; + + PlayerCharacter lead = group.getGroupLead(); + + if (lead == null) + return; + + try { + GroupManager.RefreshWholeGroupList(lead, lead.getClientConnection(), this.group); + } catch (Exception e) { + Logger.error(e); + } + + JobScheduler.getInstance().scheduleJob(this, MBServerStatics.UPDATE_GROUP_RATE); + } + + @Override + protected void _cancelJob() { + } +} diff --git a/src/engine/jobs/UpgradeBuildingJob.java b/src/engine/jobs/UpgradeBuildingJob.java new file mode 100644 index 00000000..c9032af9 --- /dev/null +++ b/src/engine/jobs/UpgradeBuildingJob.java @@ -0,0 +1,53 @@ +package engine.jobs; + +import engine.job.AbstractScheduleJob; +import engine.objects.Building; +import org.pmw.tinylog.Logger; + +/* + * This class handles upgrading of buildings, swapping the + * appropriate mesh according to the building's blueprint. + * @Author + */ +public class UpgradeBuildingJob extends AbstractScheduleJob { + + private final Building rankingBuilding; + + public UpgradeBuildingJob(Building building) { + super(); + this.rankingBuilding = building; + + } + + @Override + protected void doJob() { + + + // Must have a building to rank! + + if (rankingBuilding == null) { + Logger.error("Attempting to rank null building"); + return; + } + + // Make sure the building is currently set to upgrade + // (Duplicate job sanity check) + + if (rankingBuilding.isRanking() == false) + return; + + // SetCurrentRank also changes the mesh and maxhp + // accordingly for buildings with blueprints + + rankingBuilding.setRank(rankingBuilding.getRank() + 1); + + // Reload the object + + + } + + @Override + protected void _cancelJob() { + } + +} diff --git a/src/engine/jobs/UpgradeNPCJob.java b/src/engine/jobs/UpgradeNPCJob.java new file mode 100644 index 00000000..d080746c --- /dev/null +++ b/src/engine/jobs/UpgradeNPCJob.java @@ -0,0 +1,62 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.job.AbstractScheduleJob; +import engine.objects.AbstractCharacter; +import engine.objects.Mob; +import engine.objects.NPC; + +public class UpgradeNPCJob extends AbstractScheduleJob { + + private final AbstractCharacter rankingAC; + boolean success; + + public UpgradeNPCJob(AbstractCharacter ac) { + super(); + this.rankingAC = ac; + } + + @Override + protected void doJob() { + + int newRank; + + if (this.rankingAC.getObjectType() == GameObjectType.NPC) { + + + if (this.rankingAC == null) //NPC could've been removed... + return; + newRank = (this.rankingAC.getRank() * 10) + 10; + + + ((NPC) this.rankingAC).setRank(newRank); + ((NPC) this.rankingAC).setUpgradeDateTime(null); + } else if (this.rankingAC.getObjectType() == GameObjectType.Mob) { + if (this.rankingAC == null) //NPC could've been removed... + return; + newRank = (this.rankingAC.getRank() * 10) + 10; + + + ((Mob) this.rankingAC).setRank(newRank); + Mob.setUpgradeDateTime((Mob) this.rankingAC, null); + WorldGrid.updateObject(this.rankingAC); + + } + + } + + @Override + protected void _cancelJob() { + } + +} diff --git a/src/engine/jobs/UseItemJob.java b/src/engine/jobs/UseItemJob.java new file mode 100644 index 00000000..008e74a7 --- /dev/null +++ b/src/engine/jobs/UseItemJob.java @@ -0,0 +1,57 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.PowersManager; +import engine.job.AbstractScheduleJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.PowersBase; + +public class UseItemJob extends AbstractScheduleJob { + + private final AbstractCharacter ac; + private final AbstractWorldObject target; + private final PowersBase pb; + private final int trains; + private final int liveCounter; + + public UseItemJob(AbstractCharacter ac, AbstractWorldObject target, PowersBase pb, int trains, int liveCounter) { + super(); + this.ac = ac; + this.target = target; + this.pb = pb; + this.trains = trains; + this.liveCounter = liveCounter; + } + + @Override + protected void doJob() { + PowersManager.finishApplyPower(ac, target, Vector3fImmutable.ZERO, pb, trains, liveCounter); + } + + @Override + protected void _cancelJob() { + this.ac.setItemCasting(false); + } + + public PowersBase getPowersBase() { + return this.pb; + } + + public int getTrains() { + return this.trains; + } + + public AbstractWorldObject getTarget() { + return this.target; + } +} diff --git a/src/engine/jobs/UseMobPowerJob.java b/src/engine/jobs/UseMobPowerJob.java new file mode 100644 index 00000000..3b827a34 --- /dev/null +++ b/src/engine/jobs/UseMobPowerJob.java @@ -0,0 +1,57 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.PowersManager; +import engine.job.AbstractScheduleJob; +import engine.net.client.msg.PerformActionMsg; +import engine.objects.Mob; +import engine.powers.PowersBase; + +public class UseMobPowerJob extends AbstractScheduleJob { + + private final Mob caster; + private final PerformActionMsg msg; + private final int token; + private final PowersBase pb; + private final int casterLiveCounter; + private final int targetLiveCounter; + + public UseMobPowerJob(Mob caster, PerformActionMsg msg, int token, PowersBase pb, int casterLiveCounter, int targetLiveCounter) { + super(); + this.caster = caster; + this.msg = msg; + this.token = token; + this.pb = pb; + this.casterLiveCounter = casterLiveCounter; + this.targetLiveCounter = targetLiveCounter; + } + + @Override + protected void doJob() { + PowersManager.finishUseMobPower(this.msg, this.caster, casterLiveCounter, targetLiveCounter); + } + + @Override + protected void _cancelJob() { + } + + public PowersBase getPowersBase() { + return this.pb; + } + + public int getToken() { + return this.token; + } + + public PerformActionMsg getMsg() { + return this.msg; + } +} diff --git a/src/engine/jobs/UsePowerJob.java b/src/engine/jobs/UsePowerJob.java new file mode 100644 index 00000000..c9d4c18f --- /dev/null +++ b/src/engine/jobs/UsePowerJob.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.jobs; + +import engine.gameManager.PowersManager; +import engine.job.AbstractScheduleJob; +import engine.net.client.msg.PerformActionMsg; +import engine.objects.PlayerCharacter; +import engine.powers.PowersBase; + +public class UsePowerJob extends AbstractScheduleJob { + + private final PlayerCharacter pc; + private final PerformActionMsg msg; + private final int token; + private final PowersBase pb; + private final int casterLiveCounter; + private final int targetLiveCounter; + + public UsePowerJob(PlayerCharacter pc, PerformActionMsg msg, int token, PowersBase pb, int casterLiveCounter, int targetLiveCounter) { + super(); + this.pc = pc; + this.msg = msg; + this.token = token; + this.pb = pb; + this.casterLiveCounter = casterLiveCounter; + this.targetLiveCounter = targetLiveCounter; + } + + @Override + protected void doJob() { + PowersManager.finishUsePower(this.msg, this.pc, casterLiveCounter, targetLiveCounter); + } + + @Override + protected void _cancelJob() { + //cast stopped early, reset recycle timer + PowersManager.finishRecycleTime(this.msg, this.pc, true); + } + + public PowersBase getPowersBase() { + return this.pb; + } + + public int getToken() { + return this.token; + } + + public PerformActionMsg getMsg() { + return this.msg; + } +} diff --git a/src/engine/loot/BootySetEntry.java b/src/engine/loot/BootySetEntry.java new file mode 100644 index 00000000..d909a413 --- /dev/null +++ b/src/engine/loot/BootySetEntry.java @@ -0,0 +1,37 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.loot; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class BootySetEntry { + + public String bootyType; + public int lowGold; + public int highGold; + public int itemBase; + public int genTable; + public float dropChance; + + /** + * ResultSet Constructor + */ + + public BootySetEntry(ResultSet rs) throws SQLException { + this.bootyType = (rs.getString("bootyType")); + this.lowGold = (rs.getInt("lowGold")); + this.highGold = (rs.getInt("highGold")); + this.itemBase = (rs.getInt("itemBase")); + this.genTable = (rs.getInt("genTable")); + this.dropChance = (rs.getFloat("dropChance")); + } + +} diff --git a/src/engine/loot/GenTableEntry.java b/src/engine/loot/GenTableEntry.java new file mode 100644 index 00000000..3be47c50 --- /dev/null +++ b/src/engine/loot/GenTableEntry.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.loot; + +import engine.gameManager.LootManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class GenTableEntry { + public int minRoll; + public int maxRoll; + public int itemTableID; + public int pModTable; + public int sModTable; + + public GenTableEntry(ResultSet rs) throws SQLException { + this.minRoll = rs.getInt("minRoll"); + this.maxRoll = rs.getInt("maxRoll"); + this.itemTableID = rs.getInt("itemTableID"); + this.pModTable = rs.getInt("pModTableID"); + this.sModTable = rs.getInt("sModTableID"); + } + + public static GenTableEntry rollTable(int genTable, int roll, float dropRate) { + + GenTableEntry genTableEntry = null; + List genTableEntryList; + + genTableEntryList = LootManager._genTables.get(genTable); + + for (GenTableEntry iteration : genTableEntryList) + if (roll >= iteration.minRoll && roll <= iteration.maxRoll) + genTableEntry = iteration; + + return genTableEntry; + } +} diff --git a/src/engine/loot/ItemTableEntry.java b/src/engine/loot/ItemTableEntry.java new file mode 100644 index 00000000..9147f217 --- /dev/null +++ b/src/engine/loot/ItemTableEntry.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.loot; + +import engine.gameManager.LootManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class ItemTableEntry { + public int minRoll; + public int maxRoll; + public int cacheID; + public int minSpawn; + public int maxSpawn; + + public ItemTableEntry(ResultSet rs) throws SQLException { + this.minRoll = rs.getInt("minRoll"); + this.maxRoll = rs.getInt("maxRoll"); + this.cacheID = rs.getInt("itemBaseUUID"); + this.minSpawn = rs.getInt("minSpawn"); + this.maxSpawn = rs.getInt("maxSpawn"); + } + + public static ItemTableEntry rollTable(int itemTable, int roll) { + + ItemTableEntry itemTableEntry = null; + List itemTableEntryList; + + itemTableEntryList = LootManager._itemTables.get(itemTable); + + for (ItemTableEntry iteration : itemTableEntryList) + if (roll >= iteration.minRoll && roll <= iteration.maxRoll) + itemTableEntry = iteration; + + return itemTableEntry; + } +} diff --git a/src/engine/loot/ModTableEntry.java b/src/engine/loot/ModTableEntry.java new file mode 100644 index 00000000..86697880 --- /dev/null +++ b/src/engine/loot/ModTableEntry.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.loot; + +import engine.gameManager.LootManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class ModTableEntry { + public int minRoll; + public int maxRoll; + public String action; + public int level; + + public ModTableEntry(ResultSet rs) throws SQLException { + this.minRoll = rs.getInt("minRoll"); + this.maxRoll = rs.getInt("maxRoll"); + this.action = rs.getString("action"); + this.level = rs.getInt("level"); + } + + public static ModTableEntry rollTable(int modTablwe, int roll) { + + ModTableEntry modTableEntry = null; + List itemTableEntryList; + + itemTableEntryList = LootManager._modTables.get(modTablwe); + + for (ModTableEntry iteration : itemTableEntryList) + if (roll >= iteration.minRoll && roll <= iteration.maxRoll) + modTableEntry = iteration; + + return modTableEntry; + } +} diff --git a/src/engine/loot/ModTypeTableEntry.java b/src/engine/loot/ModTypeTableEntry.java new file mode 100644 index 00000000..8ed23ace --- /dev/null +++ b/src/engine/loot/ModTypeTableEntry.java @@ -0,0 +1,41 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.loot; + +import engine.gameManager.LootManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class ModTypeTableEntry { + public int minRoll; + public int maxRoll; + public int modTableID; + + public ModTypeTableEntry(ResultSet rs) throws SQLException { + this.minRoll = rs.getInt("minRoll"); + this.maxRoll = rs.getInt("maxRoll"); + this.modTableID = rs.getInt("subTableID"); + } + + public static ModTypeTableEntry rollTable(int modTablwe, int roll) { + + ModTypeTableEntry modTypeTableEntry = null; + List modTypeTableEntryList; + + modTypeTableEntryList = LootManager._modTypeTables.get(modTablwe); + + for (ModTypeTableEntry iteration : modTypeTableEntryList) + if (roll >= iteration.minRoll && roll <= iteration.maxRoll) + modTypeTableEntry = iteration; + + return modTypeTableEntry; + } +} diff --git a/src/engine/math/AtomicFloat.java b/src/engine/math/AtomicFloat.java new file mode 100644 index 00000000..b5a1a808 --- /dev/null +++ b/src/engine/math/AtomicFloat.java @@ -0,0 +1,101 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import java.util.concurrent.atomic.AtomicInteger; + +public class AtomicFloat { + + private final AtomicInteger fl; + + public AtomicFloat() { + fl = new AtomicInteger(Float.floatToIntBits(0f)); + } + + public AtomicFloat(float value) { + fl = new AtomicInteger(Float.floatToIntBits(value)); + } + + public float addAndGet(float delta) { + int oldValue = fl.get(); + while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + delta))) { + oldValue = fl.get(); + } + return fl.get(); + } + + public boolean compareAndSet(float oldVal, float newVal) { + return fl.compareAndSet(Float.floatToIntBits(oldVal), Float.floatToIntBits(newVal)); + } + + public float decrementAndGet() { + int oldValue = fl.get(); + while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) - 1f))) { + oldValue = fl.get(); + } + return fl.get(); + } + + public float get() { + return Float.intBitsToFloat(fl.get()); + } + + public float getAndAdd(float delta) { + int oldValue = fl.get(); + while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + delta))) { + oldValue = fl.get(); + } + return oldValue; + } + + public float getAndIncrement() { + int oldValue = fl.get(); + while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + 1f))) { + oldValue = fl.get(); + } + return oldValue; + } + + public float getAndDecrement(float delta) { + int oldValue = fl.get(); + while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) - 1f))) { + oldValue = fl.get(); + } + return oldValue; + } + + public float getAndSet(float value) { + return Float.intBitsToFloat(fl.getAndSet(Float.floatToIntBits(value))); + } + + public float incrementAndGet() { + int oldValue = fl.get(); + while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + 1f))) { + oldValue = fl.get(); + } + return fl.get(); + } + + public void lazySet(float value) { + fl.lazySet(Float.floatToIntBits(value)); + } + + public void set(float value) { + fl.set(Float.floatToIntBits(value)); + } + + @Override + public String toString() { + return fl.toString(); + } + + public boolean weakCompareAndSet(float oldVal, float newVal) { + return fl.weakCompareAndSet(Float.floatToIntBits(oldVal), Float.floatToIntBits(newVal)); + } +} diff --git a/src/engine/math/Bounds.java b/src/engine/math/Bounds.java new file mode 100644 index 00000000..04998e1c --- /dev/null +++ b/src/engine/math/Bounds.java @@ -0,0 +1,566 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import engine.InterestManagement.WorldGrid; +import engine.gameManager.ZoneManager; +import engine.net.client.msg.PlaceAssetMsg.PlacementInfo; +import engine.objects.*; +import engine.server.MBServerStatics; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * This class contains all methods of storing bounds + * information within MagicBane and performing collision + * detection against them. + *

+ * These objects are essentially an AABB, given rotations + * in MagicBane for placed objects come in a quantum of 90. + */ + +public class Bounds { + + private static final LinkedBlockingQueue boundsPool = new LinkedBlockingQueue<>(); + public static HashMap meshBoundsCache = new HashMap<>(); + + private Vector2f origin = new Vector2f(); + private Vector2f halfExtents = new Vector2f(); + private float rotation; + private float rotationDegrees = 0; + private Quaternion quaternion; + private boolean flipExtents; + + private ArrayList regions = new ArrayList<>(); + private ArrayList colliders = new ArrayList<>(); + + // Default constructor + + public Bounds() { + + origin.zero(); + halfExtents.zero(); + rotation = 0.0f; + flipExtents = false; + } + + public static Bounds borrow() { + Bounds outBounds; + + outBounds = boundsPool.poll(); + + if (outBounds == null) + outBounds = new Bounds(); + + return outBounds; + } + + // Identity Bounds at location + public static void zero(Bounds bounds) { + bounds.origin.zero(); + bounds.halfExtents.zero(); + bounds.rotation = 0.0f; + bounds.flipExtents = false; + } + + public static boolean collide(Vector3fImmutable location, Bounds targetBounds) { + + if (targetBounds == null) + return false; + + boolean collisionState = false; + Bounds identityBounds = Bounds.borrow(); + identityBounds.setBounds(location); + + collisionState = collide(targetBounds, identityBounds, 0.0f); + identityBounds.release(); + return collisionState; + } + + public static boolean collide(Vector3fImmutable location, Building targetBuilding) { + + boolean collisionState = false; + Bounds targetBounds = targetBuilding.getBounds(); + + if (targetBounds == null) + return false; + Bounds identityBounds = Bounds.borrow(); + identityBounds.setBounds(location); + + collisionState = collide(targetBounds, identityBounds, 0.1f); + identityBounds.release(); + return collisionState; + } + + public static boolean collide(Bounds sourceBounds, Bounds targetBounds, float threshold) { + + float deltaX; + float deltaY; + float extentX; + float extentY; + float sourceExtentX; + float sourceExtentY; + float targetExtentX; + float targetExtentY; + + deltaX = Math.abs(sourceBounds.origin.x - targetBounds.origin.x); + deltaY = Math.abs(sourceBounds.origin.y - targetBounds.origin.y); + + if (sourceBounds.flipExtents) { + sourceExtentX = sourceBounds.halfExtents.y; + sourceExtentY = sourceBounds.halfExtents.x; + } else { + sourceExtentX = sourceBounds.halfExtents.x; + sourceExtentY = sourceBounds.halfExtents.y; + } + if (targetBounds.flipExtents) { + targetExtentX = targetBounds.halfExtents.y; + targetExtentY = targetBounds.halfExtents.x; + } else { + targetExtentX = targetBounds.halfExtents.x; + targetExtentY = targetBounds.halfExtents.y; + } + + extentX = sourceExtentX + targetExtentX; + extentY = sourceExtentY + targetExtentY; + + // Return false on overlapping edge cases + if ((Math.abs(deltaX + threshold) < extentX)) + if ((Math.abs(deltaY + threshold) < extentY)) + return true; + + return false; + + } + + public static boolean collide(PlacementInfo sourceInfo, Building targetBuilding) { + + Bounds sourceBounds; + Bounds targetBounds; + + boolean collisionState = false; + + // Early exit sanity check. Can't quite collide against nothing + + if ((sourceInfo == null) || (targetBuilding == null)) + return false; + + sourceBounds = Bounds.borrow(); + sourceBounds.setBounds(sourceInfo); + + // WARNING: DO NOT EVER RELEASE THESE WORLDOBJECT BOUNDS + // THEY ARE NOT IMMUTABLE + + targetBounds = targetBuilding.getBounds(); + + // If target building has no bounds, we certainly cannot collide. + // Note: We remove and release bounds objects to the pool when + // buildings are destroyed. + + if (targetBounds == null) + return false; + + collisionState = collide(sourceBounds, targetBounds, .1f); + + // Release bounds and return collision state + + sourceBounds.release(); + return collisionState; + } + + public static boolean collide(Bounds bounds, Vector3fImmutable start, Vector3fImmutable end) { + boolean collide = false; + for (Colliders collider : bounds.colliders) { + + collide = linesTouching(collider.startX, collider.startY, collider.endX, collider.endY, start.x, start.z, end.x, end.z); + + if (collide) + break; + + + } + + return collide; + } + + //used for wall collision with players. + public static Vector3fImmutable PlayerBuildingCollisionPoint(PlayerCharacter player, Vector3fImmutable start, Vector3fImmutable end) { + Vector3fImmutable collidePoint = null; + + //player can fly over walls when at max altitude. skip collision checks. + if (player.getAltitude() >= 60) + return null; + + + float distance = player.getLoc().distance2D(end); + // Players should not be able to move more than 2000 units at a time, stop them dead in their tracks if they do. (hacks) + if (distance > 2000) + return player.getLoc(); + + + HashSet awoList = WorldGrid.getObjectsInRangePartial(player, distance + 1000, MBServerStatics.MASK_BUILDING); + float collideDistance = 0; + float lastDistance = -1; + + + for (AbstractWorldObject awo : awoList) { + + Building building = (Building) awo; + + + //player is inside building region, skip collision check. we only do collision from the outside. + if (player.region != null && player.region.parentBuildingID == building.getObjectUUID()) + continue; + if (building.getBounds().colliders == null) + continue; + + for (Colliders collider : building.getBounds().colliders) { + + //links are what link together buildings, allow players to run through them only if they are in a building already. + if (collider.isLink() && player.region != null) + continue; + if (collider.getDoorID() != 0 && building.isDoorOpen(collider.getDoorID())) + continue; + + Vector3fImmutable tempCollidePoint = lineIntersection(collider.startX, collider.startY, collider.endX, collider.endY, start.x, start.z, end.x, end.z); + + //didnt collide, skip distance checks. + if (tempCollidePoint == null) + continue; + + //first collision detection, inititialize all variables. + if (lastDistance == -1) { + collideDistance = start.distance2D(tempCollidePoint); + lastDistance = collideDistance; + collidePoint = tempCollidePoint; + } else + //get closest collide point. + collideDistance = start.distance2D(tempCollidePoint); + + if (collideDistance < lastDistance) { + lastDistance = collideDistance; + collidePoint = tempCollidePoint; + } + } + } + + + // + if (collidePoint != null) { + + if (collideDistance >= 2) + collidePoint = player.getFaceDir().scaleAdd(-2f, new Vector3fImmutable((float) collidePoint.getX(), end.y, (float) collidePoint.getZ())); + else + collidePoint = player.getLoc(); + } + + + return collidePoint; + } + + public static boolean linesTouching(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { + float denominator = ((x2 - x1) * (y4 - y3)) - ((y2 - y1) * (x4 - x3)); + float numerator1 = ((y1 - y3) * (x4 - x3)) - ((x1 - x3) * (y4 - y3)); + float numerator2 = ((y1 - y3) * (x2 - x1)) - ((x1 - x3) * (y2 - y1)); + + // Detect coincident lines (has a problem, read below) + if (denominator == 0) + return numerator1 == 0 && numerator2 == 0; + + float r = numerator1 / denominator; + float s = numerator2 / denominator; + + return (r >= 0 && r <= 1) && (s >= 0 && s <= 1); + } + + public static Vector3fImmutable lineIntersection(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { + + // calculate the distance to intersection point + float uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)); + float uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)); + + // if uA and uB are between 0-1, lines are colliding + if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) { + return new Vector3fImmutable(x1 + (uA * (x2 - x1)), 0, y1 + (uA * (y2 - y1))); + } + return null; + } + + private static boolean calculateFlipExtents(Bounds bounds) { + + int degrees; + double radian = 0; + if (bounds.quaternion != null) { + radian = bounds.quaternion.angleY; + } + + degrees = (int) Math.toDegrees(radian); + bounds.rotationDegrees = degrees; + if (degrees < 0) + degrees += 360; + return (degrees >= 85 && degrees <= 95) || + (degrees >= 265 && degrees <= 275); + + } + + public static Vector3f getRotatedPoint(Vector3f point, float centerX, float centerZ, float angle) { + + //TRANSLATE TO ORIGIN + float x1 = point.getX() - centerX; + float y1 = point.getZ() - centerZ; + + //APPLY ROTATION + float temp_x1 = (float) (x1 * Math.cos(angle) - y1 * Math.sin(angle)); + float temp_z1 = (float) (x1 * Math.sin(angle) + y1 * Math.cos(angle)); + + temp_x1 += centerX; + temp_z1 += centerZ; + + return new Vector3f(temp_x1, point.y, temp_z1); + + } + + public void release() { + Bounds.zero(this); + boundsPool.add(this); + + } + + // Method detects overlap of two given Bounds objects. + // Just your generic AABB collision algorythm. + + public void setBounds(Vector2f origin, Vector2f extents, float rotation) { + + this.origin.set(origin); + this.halfExtents.set(extents); + this.rotation = rotation; + + this.flipExtents = Bounds.calculateFlipExtents(this); + + } + + public void setBounds(PlacementInfo sourceInfo) { + + Blueprint sourceBlueprint; + + sourceBlueprint = Blueprint.getBlueprint(sourceInfo.getBlueprintUUID()); + this.origin.set(sourceInfo.getLoc().x, sourceInfo.getLoc().z); + this.halfExtents.set(sourceBlueprint.getExtents()); + + this.quaternion = new Quaternion(sourceInfo.getRot().x, sourceInfo.getRot().y, sourceInfo.getRot().z, sourceInfo.getW()); + this.rotation = sourceInfo.getRot().y; + this.flipExtents = Bounds.calculateFlipExtents(this); + + } + + public void setBounds(Bounds sourceBounds) { + + origin.set(sourceBounds.origin); + halfExtents.set(sourceBounds.halfExtents); + this.rotation = sourceBounds.rotation; + this.flipExtents = sourceBounds.flipExtents; + + } + + public void setBounds(AbstractCharacter sourcePlayer) { + + this.origin.set(sourcePlayer.getLoc().x, sourcePlayer.getLoc().z); + this.halfExtents.set(.5f, .5f); + this.rotation = 0; + this.flipExtents = false; + + } + + public void setBounds(Vector3fImmutable sourceLocation) { + + this.origin.set(sourceLocation.x, sourceLocation.z); + this.halfExtents.set(.5f, .5f); + this.rotation = 0; + this.flipExtents = false; + + } + + public void setBounds(Vector3fImmutable sourceLocation, float halfExtent) { + + this.origin.set(sourceLocation.x, sourceLocation.z); + this.halfExtents.set(halfExtent, halfExtent); + this.rotation = 0; + this.flipExtents = false; + + } + + public void setBounds(Building building) { + + Blueprint blueprint; + MeshBounds meshBounds; + int halfExtentX; + int halfExtentY; + // Need a blueprint for proper bounds + + blueprint = building.getBlueprint(); + + this.quaternion = new Quaternion(building.getRot().x, building.getRot().y, building.getRot().z, building.getw()); + + // Calculate Bounds for non-blueprint objects + + if (blueprint == null) { + + // If a mesh is a non-blueprint structure then we calculate + // it's bounding box based upon defaults from original source + // lookup. + + meshBounds = meshBoundsCache.get(building.getMeshUUID()); + this.origin.set(building.getLoc().x, building.getLoc().z); + + + // Magicbane uses half halfExtents + + if (meshBounds == null) { + halfExtentX = 1; + halfExtentY = 1; + } else { + + float halfExtent = Math.max((meshBounds.maxX - meshBounds.minX) / 2, (meshBounds.maxZ - meshBounds.minZ) / 2); + halfExtentX = Math.round(halfExtent); + halfExtentY = Math.round(halfExtent); + } + + + // The rotation is reset after the new aabb is calculated. + + this.rotation = building.getRot().y; + // Caclculate and set the new half halfExtents for the rotated bounding box + // and reset the rotation to 0 for this bounds. + this.halfExtents.set(halfExtentX, (halfExtentY)); + this.rotation = 0; + + this.setRegions(building); + this.setColliders(building); + return; + } + + this.origin.set(building.getLoc().x, building.getLoc().z); + this.rotation = building.getRot().y; + this.halfExtents.set(blueprint.getExtents()); + this.flipExtents = Bounds.calculateFlipExtents(this); + + + this.setRegions(building); + this.setColliders(building); + + } + + public void modify(float x, float y, float extents) { + this.origin.x = x; + this.origin.y = y; + this.halfExtents.x = extents; + this.halfExtents.y = extents; + } + + /** + * @return the origin + */ + public Vector2f getOrigin() { + return origin; + } + + /** + * @return the halfExtents + */ + public Vector2f getHalfExtents() { + return halfExtents; + } + + /** + * @return the rotation + */ + public float getRotation() { + return rotation; + } + + /** + * @param rotation the rotation to set + */ + public void setRotation(float rotation) { + this.rotation = rotation; + } + + public void setColliders(Building building) { + //Collidables are for player movement collision + ArrayList tempList = StaticColliders._staticColliders.get(building.getMeshUUID()); + + ArrayList tempColliders = new ArrayList<>(); + if (tempList != null) { + + for (StaticColliders staticCollider : tempList) { + + ArrayList regionPoints = new ArrayList<>(); + + Vector3f colliderStart = new Vector3f(staticCollider.getStartX(), 0, staticCollider.getStartY()); + Vector3f colliderEnd = new Vector3f(staticCollider.getEndX(), 0, staticCollider.getEndY()); + Vector3f worldStart = ZoneManager.convertLocalToWorld(building, colliderStart, this); + Vector3f worldEnd = ZoneManager.convertLocalToWorld(building, colliderEnd, this); + tempColliders.add(new Colliders(worldStart.x, worldStart.z, worldEnd.x, worldEnd.z, staticCollider.getDoorID(), staticCollider.isLink())); + } + + } + + + this.colliders = tempColliders; + } + + public ArrayList getRegions() { + return regions; + } + + public void setRegions(Building building) { + //Collidables are for player movement collision + ArrayList tempList = BuildingRegions._staticRegions.get(building.getMeshUUID()); + + ArrayList tempRegions = new ArrayList<>(); + if (tempList != null) { + + for (BuildingRegions buildingRegion : tempList) { + + ArrayList regionPoints = new ArrayList<>(); + + Vector3f centerPoint = ZoneManager.convertLocalToWorld(building, buildingRegion.center, this); + for (Vector3f point : buildingRegion.getRegionPoints()) { + Vector3f rotatedPoint = ZoneManager.convertLocalToWorld(building, point, this); + regionPoints.add(rotatedPoint); + } + tempRegions.add(new Regions(regionPoints, buildingRegion.getLevel(), buildingRegion.getRoom(), buildingRegion.isOutside(), buildingRegion.isExitRegion(), buildingRegion.isStairs(), centerPoint, building.getObjectUUID())); + } + + } + + + this.regions = tempRegions; + } + + public void setRegions(ArrayList regions) { + this.regions = regions; + } + + public float getRotationDegrees() { + return rotationDegrees; + } + + public boolean isFlipExtents() { + return flipExtents; + } + + public Quaternion getQuaternion() { + return quaternion; + } + +} diff --git a/src/engine/math/FastMath.java b/src/engine/math/FastMath.java new file mode 100644 index 00000000..c82f90e3 --- /dev/null +++ b/src/engine/math/FastMath.java @@ -0,0 +1,646 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import java.util.Calendar; +import java.util.concurrent.ThreadLocalRandom; + +/** + * FastMath provides 'fast' math approximations and float + * equivalents of Math functions. These are all used as static values and + * functions. + * + * @author Various + */ + +final public class FastMath { + + /** + * A "close to zero" double epsilon value for use + */ + public static final double DBL_EPSILON = 2.220446049250313E-16d; + /** + * A "close to zero" float epsilon value for use + */ + public static final float FLT_EPSILON = 1.1920928955078125E-7f; + /** + * A "close to zero" float epsilon value for use + */ + public static final float ZERO_TOLERANCE = 0.0001f; + public static final float ONE_THIRD = 1f / 3f; + /** + * The value PI as a float. (180 degrees) + */ + public static final float PI = (float) Math.PI; + /** + * The value 2PI as a float. (360 degrees) + */ + public static final float TWO_PI = 2.0f * PI; + /** + * The value 1/(2PI) as a float. + */ + public static final float INV_TWO_PI = 1.0f / TWO_PI; + /** + * The value PI/2 as a float. (90 degrees) + */ + public static final float HALF_PI = 0.5f * PI; + + /** + * The value PI/4 as a float. (45 degrees) + */ + public static final float QUARTER_PI = 0.25f * PI; + + /** + * The value 1/PI as a float. + */ + public static final float INV_PI = 1.0f / PI; + /** + * A value to multiply a degree value by, to convert it to radians. + */ + public static final float DEG_TO_RAD = PI / 180.0f; + /** + * A value to multiply a radian value by, to convert it to degrees. + */ + public static final float RAD_TO_DEG = 180.0f / PI; + + private FastMath() { + } + + /** + * Returns true if the number is a power of 2 (2,4,8,16...) + *

+ * A good implementation found on the Java boards. note: a number is a power + * of two if and only if it is the smallest number with that number of + * significant bits. Therefore, if you subtract 1, you know that the new + * number will have fewer bits, so ANDing the original number with anything + * less than it will give 0. + * + * @param number The number to test. + * @return True if it is a power of two. + */ + public static boolean isPowerOfTwo(int number) { + return (number > 0) && (number & (number - 1)) == 0; + } + + public static int nearestPowerOfTwo(int number) { + return (int) Math.pow(2, Math.ceil(Math.log(number) / Math.log(2))); + } + + public static boolean between(float i, float minValueInclusive, float maxValueInclusive) { + return (i >= minValueInclusive && i <= maxValueInclusive); + } + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param percent Percent value to use. + * @param startValue Begining value. 0% of f + * @param endValue ending value. 100% of f + * @return The interpolated value between startValue and endValue. + */ + public static float LERP(float percent, float startValue, float endValue) { + if (startValue == endValue) + return startValue; + return ((1 - percent) * startValue) + (percent * endValue); + } + + /** + * Returns the arc cosine of an angle given in radians.
+ * Special cases: + *

    + *
  • If fValue is smaller than -1, then the result is PI. + *
  • If the argument is greater than 1, then the result is 0. + *
+ * + * @param fValue The angle, in radians. + * @return fValue's acos + * @see java.lang.Math#acos(double) + */ + public static float acos(float fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) + return (float) Math.acos(fValue); + + return 0.0f; + } + + return PI; + } + + /** + * Returns the arc sine of an angle given in radians.
+ * Special cases: + *
    + *
  • If fValue is smaller than -1, then the result is -HALF_PI. + *
  • If the argument is greater than 1, then the result is HALF_PI. + *
+ * + * @param fValue The angle, in radians. + * @return fValue's asin + * @see java.lang.Math#asin(double) + */ + public static float asin(float fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) + return (float) Math.asin(fValue); + + return HALF_PI; + } + + return -HALF_PI; + } + + /** + * Returns the arc tangent of an angle given in radians.
+ * + * @param fValue The angle, in radians. + * @return fValue's asin + * @see java.lang.Math#atan(double) + */ + public static float atan(float fValue) { + return (float) Math.atan(fValue); + } + + /** + * A direct call to Math.atan2. + * + * @param fY + * @param fX + * @return Math.atan2(fY, fX) + * @see java.lang.Math#atan2(double, double) + */ + public static float atan2(float fY, float fX) { + return (float) Math.atan2(fY, fX); + } + + /** + * Rounds a fValue up. A call to Math.ceil + * + * @param fValue The value. + * @return The fValue rounded up + * @see java.lang.Math#ceil(double) + */ + public static float ceil(float fValue) { + return (float) Math.ceil(fValue); + } + + /** + * Fast Trig functions for x86. This forces the trig functiosn to stay + * within the safe area on the x86 processor (-45 degrees to +45 degrees) + * The results may be very slightly off from what the Math and StrictMath + * trig functions give due to rounding in the angle reduction but it will be + * very very close. + */ + public static float reduceSinAngle(float radians) { + radians %= TWO_PI; // put us in -2PI to +2PI space + if (Math.abs(radians) > PI) { // put us in -PI to +PI space + radians -= (TWO_PI); + } + if (Math.abs(radians) > HALF_PI) {// put us in -PI/2 to +PI/2 space + radians = PI - radians; + } + + return radians; + } + + /** + * Returns sine of a value. + * + * @param fValue The value to sine, in radians. + * @return The sine of fValue. + * @see java.lang.Math#sin(double) + */ + public static float sin(float fValue) { + fValue = reduceSinAngle(fValue); // limits angle to between -PI/2 and + // +PI/2 + if (Math.abs(fValue) <= Math.PI / 4) { + return (float) Math.sin(fValue); + } + + return (float) Math.cos(Math.PI / 2 - fValue); + } + + /** + * Returns cos of a value. + * + * @param fValue The value to cosine, in radians. + * @return The cosine of fValue. + * @see java.lang.Math#cos(double) + */ + public static float cos(float fValue) { + return sin(fValue + HALF_PI); + } + + /** + * Returns E^fValue + * + * @param fValue Value to raise to a power. + * @return The value E^fValue + * @see java.lang.Math#exp(double) + */ + public static float exp(float fValue) { + return (float) Math.exp(fValue); + } + + /** + * Returns Absolute value of a float. + * + * @param fValue The value to abs. + * @return The abs of the value. + * @see java.lang.Math#abs(float) + */ + public static float abs(float fValue) { + if (fValue < 0) + return -fValue; + return fValue; + } + + /** + * Returns a number rounded down. + * + * @param fValue The value to round + * @return The given number rounded down + * @see java.lang.Math#floor(double) + */ + public static float floor(float fValue) { + return (float) Math.floor(fValue); + } + + /** + * Returns 1/sqrt(fValue) + * + * @param fValue The value to process. + * @return 1/sqrt(fValue) + * @see java.lang.Math#sqrt(double) + */ + public static float invSqrt(float fValue) { + return (float) (1.0f / Math.sqrt(fValue)); + } + + /** + * Returns the log base E of a value. + * + * @param fValue The value to Logger.getInstance(). + * @return The log of fValue base E + * @see java.lang.Math#log(double) + */ + public static float log(float fValue) { + return (float) Math.log(fValue); + } + + /** + * Returns the logarithm of value with given base, calculated as + * log(value)/log(base), so that pow(base, return)==value (contributed by + * vear) + * + * @param value The value to Logger.getInstance(). + * @param base Base of logarithm. + * @return The logarithm of value with given base + */ + public static float log(float value, float base) { + return (float) (Math.log(value) / Math.log(base)); + } + + /** + * Returns a number raised to an exponent power. fBase^fExponent + * + * @param fBase The base value (IE 2) + * @param fExponent The exponent value (IE 3) + * @return base raised to exponent (IE 8) + * @see java.lang.Math#pow(double, double) + */ + public static float pow(float fBase, float fExponent) { + return (float) Math.pow(fBase, fExponent); + } + + /** + * Returns the value squared. fValue ^ 2 + * + * @param fValue The vaule to square. + * @return The square of the given value. + */ + public static float sqr(float fValue) { + return fValue * fValue; + } + + public static double sqr(double fValue) { + return fValue * fValue; + } + + /** + * Returns the square root of a given value. + * + * @param fValue The value to sqrt. + * @return The square root of the given value. + * @see java.lang.Math#sqrt(double) + */ + public static float sqrt(float fValue) { + return (float) Math.sqrt(fValue); + } + + /** + * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an + * approximate value is returned. Otherwise, a direct value is used. + * + * @param fValue The value to tangent, in radians. + * @return The tangent of fValue. + * @see java.lang.Math#tan(double) + */ + public static float tan(float fValue) { + return (float) Math.tan(fValue); + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 + * otherwise + * + * @param iValue The integer to examine. + * @return The integer's sign. + */ + public static int sign(int iValue) { + if (iValue > 0) + return 1; + + if (iValue < 0) + return -1; + + return 0; + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 + * otherwise + * + * @param fValue The float to examine. + * @return The float's sign. + */ + public static float sign(float fValue) { + return Math.signum(fValue); + } + + /** + * Given 3 points in a 2d plane, this function computes if the points going + * from A-B-C are moving counter clock wise. + * + * @param p0 Point 0. + * @param p1 Point 1. + * @param p2 Point 2. + * @return 1 If they are CCW, -1 if they are not CCW, 0 if p2 is between p0 + * and p1. + */ + public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) { + float dx1, dx2, dy1, dy2; + dx1 = p1.x - p0.x; + dy1 = p1.y - p0.y; + dx2 = p2.x - p0.x; + dy2 = p2.y - p0.y; + if (dx1 * dy2 > dy1 * dx2) + return 1; + if (dx1 * dy2 < dy1 * dx2) + return -1; + if ((dx1 * dx2 < 0) || (dy1 * dy2 < 0)) + return -1; + if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2)) + return 1; + return 0; + } + + /** + * Test if a point is inside a triangle. 1 if the point is on the ccw side, + * -1 if the point is on the cw side, and 0 if it is on neither. + * + * @param t0 First point of the triangle. + * @param t1 Second point of the triangle. + * @param t2 Third point of the triangle. + * @param p The point to test. + * @return Value 1 or -1 if inside triangle, 0 otherwise. + */ + public static int pointInsideTriangle(Vector2f t0, Vector2f t1, + Vector2f t2, Vector2f p) { + int val1 = counterClockwise(t0, t1, p); + if (val1 == 0) + return 1; + int val2 = counterClockwise(t1, t2, p); + if (val2 == 0) + return 1; + if (val2 != val1) + return 0; + int val3 = counterClockwise(t2, t0, p); + if (val3 == 0) + return 1; + if (val3 != val1) + return 0; + return val3; + } + + /** + * Returns the determinant of a 4x4 matrix. + */ + public static float determinant(double m00, double m01, double m02, + double m03, double m10, double m11, double m12, double m13, + double m20, double m21, double m22, double m23, double m30, + double m31, double m32, double m33) { + + double det01 = m20 * m31 - m21 * m30; + double det02 = m20 * m32 - m22 * m30; + double det03 = m20 * m33 - m23 * m30; + double det12 = m21 * m32 - m22 * m31; + double det13 = m21 * m33 - m23 * m31; + double det23 = m22 * m33 - m23 * m32; + return (float) (m00 * (m11 * det23 - m12 * det13 + m13 * det12) - m01 + * (m10 * det23 - m12 * det03 + m13 * det02) + m02 + * (m10 * det13 - m11 * det03 + m13 * det01) - m03 + * (m10 * det12 - m11 * det02 + m12 * det01)); + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive + * Y as up) and stores the results in the store var. + */ + public static Vector3f sphericalToCartesian(Vector3f sphereCoords, + Vector3f store) { + store.y = sphereCoords.x * FastMath.sin(sphereCoords.z); + float a = sphereCoords.x * FastMath.cos(sphereCoords.z); + store.x = a * FastMath.cos(sphereCoords.y); + store.z = a * FastMath.sin(sphereCoords.y); + + return store; + } + + /** + * Converts a point from Cartesian coordinates (using positive Y as up) to + * Spherical and stores the results in the store var. (Radius, Azimuth, + * Polar) + */ + public static Vector3f cartesianToSpherical(Vector3f cartCoords, + Vector3f store) { + if (cartCoords.x == 0) + cartCoords.x = FastMath.FLT_EPSILON; + store.x = FastMath + .sqrt((cartCoords.x * cartCoords.x) + + (cartCoords.y * cartCoords.y) + + (cartCoords.z * cartCoords.z)); + store.y = FastMath.atan(cartCoords.z / cartCoords.x); + if (cartCoords.x < 0) + store.y += FastMath.PI; + store.z = FastMath.asin(cartCoords.y / store.x); + return store; + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive + * Z as up) and stores the results in the store var. + */ + public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords, + Vector3f store) { + store.z = sphereCoords.x * FastMath.sin(sphereCoords.z); + float a = sphereCoords.x * FastMath.cos(sphereCoords.z); + store.x = a * FastMath.cos(sphereCoords.y); + store.y = a * FastMath.sin(sphereCoords.y); + + return store; + } + + /** + * Converts a point from Cartesian coordinates (using positive Z as up) to + * Spherical and stores the results in the store var. (Radius, Azimuth, + * Polar) + */ + public static Vector3f cartesianZToSpherical(Vector3f cartCoords, + Vector3f store) { + if (cartCoords.x == 0) + cartCoords.x = FastMath.FLT_EPSILON; + store.x = FastMath + .sqrt((cartCoords.x * cartCoords.x) + + (cartCoords.y * cartCoords.y) + + (cartCoords.z * cartCoords.z)); + store.z = FastMath.atan(cartCoords.z / cartCoords.x); + if (cartCoords.x < 0) + store.z += FastMath.PI; + store.y = FastMath.asin(cartCoords.y / store.x); + return store; + } + + /** + * Takes an value and expresses it in terms of min to max. + * + * @param val - the angle to normalize (in radians) + * @return the normalized angle (also in radians) + */ + public static float normalize(float val, float min, float max) { + if (Float.isInfinite(val) || Float.isNaN(val)) + return 0f; + float range = max - min; + while (val > max) + val -= range; + while (val < min) + val += range; + return val; + } + + /** + * @param x the value whose sign is to be adjusted. + * @param y the value whose sign is to be used. + * @return x with its sign changed to match the sign of y. + */ + public static float copysign(float x, float y) { + if (y >= 0 && x <= -0) + return -x; + else if (y < 0 && x >= 0) + return -x; + else + return x; + } + + /** + * Take a float input and clamp it between min and max. + * + * @param input + * @param min + * @param max + * @return clamped input + */ + public static float clamp(float input, float min, float max) { + return (input < min) ? min : (input > max) ? max : input; + } + + + /** + * Return a random 2D vector + * this needs checked + */ + public static Vector3fImmutable randomVector2D() { + float x = ((ThreadLocalRandom.current().nextFloat() * 2) - 1); + float z = ((ThreadLocalRandom.current().nextFloat() * 2) - 1); + Vector3fImmutable ret = new Vector3fImmutable(x, 0, z); + return ret.normalize(); + } + + /** + * Turns a String into an Integer hash that the client will recognize. + * + * @param s The String to hash + * @return the Integer hash + */ + public static int hash(String s) { + int out = 0, pad = 0; + for (char c : s.toCharArray()) { + if (c == ' ' || c == '\\') { + pad += 5; + continue; + } + if (c > 0x40 && c < 0x60) + out ^= _rotl((byte) 0x60, pad); + out ^= _rotl((byte) (c & 0xDF), pad); + pad += 5; + } + return out; + } + + /** + * Utility function for making hashes + */ + private static int _rotl(int value, int shift) { + if ((shift &= 31) == 0) + return value; + return (value << shift) | (value >> (32 - shift)); + } + + /** + * Gets number of seconds until next hours + */ + + public static int secondsUntilNextHour() { + Calendar cal = Calendar.getInstance(); + int minute = cal.get(Calendar.MINUTE); + int second = cal.get(Calendar.SECOND); + return 3600 - (minute * 60) - second; + } + + public static float area(float x1, float y1, float x2, float y2, float x3, float y3) { + return (float) Math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0); //Change to *.5? + } + + public static float GetDegrees(float yRot) { + double radian = Math.asin(yRot); + float degrees = (float) Math.toDegrees(radian); + + degrees *= 2; + return degrees; + } + + public static float degreesToW(float degrees) { + return (float) Math.abs(Math.cos(Math.toRadians(degrees) / 2)); + } + + public static float degreesToYRotation(float degrees) { + return (float) Math.sin(Math.toRadians(degrees) / 2); + } +} \ No newline at end of file diff --git a/src/engine/math/Matrix3f.java b/src/engine/math/Matrix3f.java new file mode 100644 index 00000000..b147f835 --- /dev/null +++ b/src/engine/math/Matrix3f.java @@ -0,0 +1,1155 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import org.pmw.tinylog.Logger; + +import java.nio.FloatBuffer; + + +/** + * Matrix3f defines a 3x3 matrix. Matrix data is maintained + * internally and is accessible via the get and set methods. Convenience methods + * are used for matrix operations as well as generating a matrix from a given + * set of values. + */ +public class Matrix3f { + public float m00, m01, m02; + public float m10, m11, m12; + public float m20, m21, m22; + + /** + * Constructor instantiates a new Matrix3f object. The initial + * values for the matrix is that of the identity matrix. + */ + public Matrix3f() { + loadIdentity(); + } + + /** + * constructs a matrix with the given values. + * + * @param m00 0x0 in the matrix. + * @param m01 0x1 in the matrix. + * @param m02 0x2 in the matrix. + * @param m10 1x0 in the matrix. + * @param m11 1x1 in the matrix. + * @param m12 1x2 in the matrix. + * @param m20 2x0 in the matrix. + * @param m21 2x1 in the matrix. + * @param m22 2x2 in the matrix. + */ + public Matrix3f(float m00, float m01, float m02, float m10, float m11, + float m12, float m20, float m21, float m22) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + + /** + * Copy constructor that creates a new Matrix3f object that is + * the same as the provided matrix. + * + * @param mat the matrix to copy. + */ + public Matrix3f(Matrix3f mat) { + copy(mat); + } + + static boolean equalIdentity(Matrix3f mat) { + if (Math.abs(mat.m00 - 1) > 1e-4) + return false; + if (Math.abs(mat.m11 - 1) > 1e-4) + return false; + if (Math.abs(mat.m22 - 1) > 1e-4) + return false; + + if (Math.abs(mat.m01) > 1e-4) + return false; + if (Math.abs(mat.m02) > 1e-4) + return false; + + if (Math.abs(mat.m10) > 1e-4) + return false; + if (Math.abs(mat.m12) > 1e-4) + return false; + + if (Math.abs(mat.m20) > 1e-4) + return false; + return !(Math.abs(mat.m21) > 1e-4); + } + + /** + * copy transfers the contents of a given matrix to this + * matrix. If a null matrix is supplied, this matrix is set to the identity + * matrix. + * + * @param matrix the matrix to copy. + */ + public void copy(Matrix3f matrix) { + if (null == matrix) { + loadIdentity(); + } else { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + } + } + + /** + * get retrieves a value from the matrix at the given position. + * If the position is invalid a Exception is thrown. + * + * @param i the row index. + * @param j the column index. + * @return the value at (i, j). + * @throws Exception + */ + public float get(int i, int j) throws Exception { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + } + } + throw new Exception("Invalid indices into matrix."); + } + + /** + * get(float[]) returns the matrix in row-major or column-major + * order. + * + * @param data The array to return the data into. This array can be 9 or 16 + * floats in size. Only the upper 3x3 are assigned to in the case + * of a 16 element array. + * @param rowMajor True for row major storage in the array (translation in + * elements 3, 7, 11 for a 4x4), false for column major + * (translation in elements 12, 13, 14 for a 4x4). + * @throws Exception + */ + public void get(float[] data, boolean rowMajor) throws Exception { + if (data.length == 9) { + if (rowMajor) { + data[0] = m00; + data[1] = m01; + data[2] = m02; + data[3] = m10; + data[4] = m11; + data[5] = m12; + data[6] = m20; + data[7] = m21; + data[8] = m22; + } else { + data[0] = m00; + data[1] = m10; + data[2] = m20; + data[3] = m01; + data[4] = m11; + data[5] = m21; + data[6] = m02; + data[7] = m12; + data[8] = m22; + } + } else if (data.length == 16) { + if (rowMajor) { + data[0] = m00; + data[1] = m01; + data[2] = m02; + data[4] = m10; + data[5] = m11; + data[6] = m12; + data[8] = m20; + data[9] = m21; + data[10] = m22; + } else { + data[0] = m00; + data[1] = m10; + data[2] = m20; + data[4] = m01; + data[5] = m11; + data[6] = m21; + data[8] = m02; + data[9] = m12; + data[10] = m22; + } + } else { + throw new Exception("Array size must be 9 or 16 in Matrix3f.get()."); + } + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @return the column specified by the index. + * @throws Exception + */ + public Vector3f getColumn(int i) throws Exception { + return getColumn(i, null); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + * @throws Exception + */ + public Vector3f getColumn(int i, Vector3f store) throws Exception { + if (store == null) + store = new Vector3f(); + switch (i) { + case 0: + store.x = m00; + store.y = m10; + store.z = m20; + break; + case 1: + store.x = m01; + store.y = m11; + store.z = m21; + break; + case 2: + store.x = m02; + store.y = m12; + store.z = m22; + break; + default: + throw new Exception("Invalid column index. " + i); + } + return store; + } + + /** + * getColumn returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f object. + * + * @param i the row to retrieve. Must be between 0 and 2. + * @return the row specified by the index. + * @throws Exception + */ + public Vector3f getRow(int i) throws Exception { + return getRow(i, null); + } + + /** + * getRow returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f object. + * + * @param i the row to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the row specified by the index. + * @throws Exception + */ + public Vector3f getRow(int i, Vector3f store) throws Exception { + if (store == null) + store = new Vector3f(); + switch (i) { + case 0: + store.x = m00; + store.y = m01; + store.z = m02; + break; + case 1: + store.x = m10; + store.y = m11; + store.z = m12; + break; + case 2: + store.x = m20; + store.y = m21; + store.z = m22; + break; + default: + throw new Exception("Invalid row index. " + i); + } + return store; + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, starting at current position. Must have + * room for 9 more floats. + * @return matrix data as a FloatBuffer. (position is advanced by 9 and any + * limit set is not changed). + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb) { + fb.put(m00).put(m01).put(m02); + fb.put(m10).put(m11).put(m12); + fb.put(m20).put(m21).put(m22); + return fb; + } + + /** + * setColumn sets a particular column of this matrix to that + * represented by the provided vector. + * + * @param i the column to set. + * @param column the data to set. + * @throws Exception + */ + public void setColumn(int i, Vector3f column) throws Exception { + + if (column == null) { + return; + } + switch (i) { + case 0: + m00 = column.x; + m10 = column.y; + m20 = column.z; + break; + case 1: + m01 = column.x; + m11 = column.y; + m21 = column.z; + break; + case 2: + m02 = column.x; + m12 = column.y; + m22 = column.z; + break; + default: + throw new Exception("Invalid column index. " + i); + } + } + + /** + * setRow sets a particular row of this matrix to that + * represented by the provided vector. + * + * @param i the row to set. + * @param row the data to set. + * @throws Exception + */ + public void setRow(int i, Vector3f row) throws Exception { + + if (row == null) { + return; + } + switch (i) { + case 0: + m00 = row.x; + m01 = row.y; + m02 = row.z; + break; + case 1: + m10 = row.x; + m11 = row.y; + m12 = row.z; + break; + case 2: + m20 = row.x; + m21 = row.y; + m22 = row.z; + break; + default: + throw new Exception("Invalid row index. " + i); + } + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a Exception is thrown. + * + * @param i the row index. + * @param j the column index. + * @param value the value for (i, j). + * @throws Exception + */ + public void set(int i, int j, float value) throws Exception { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return; + case 1: + m01 = value; + return; + case 2: + m02 = value; + return; + } + case 1: + switch (j) { + case 0: + m10 = value; + return; + case 1: + m11 = value; + return; + case 2: + m12 = value; + return; + } + case 2: + switch (j) { + case 0: + m20 = value; + return; + case 1: + m21 = value; + return; + case 2: + m22 = value; + return; + } + } + throw new Exception("Invalid indices into matrix."); + } + + /** + * set sets the values of the matrix to those supplied by the + * 3x3 two dimenion array. + * + * @param matrix the new values of the matrix. + * @throws Exception if the array is not of size 9. + */ + public void set(float[][] matrix) throws Exception { + if (matrix.length != 3 || matrix[0].length != 3) { + throw new Exception("Array must be of size 9."); + } + + m00 = matrix[0][0]; + m01 = matrix[0][1]; + m02 = matrix[0][2]; + m10 = matrix[1][0]; + m11 = matrix[1][1]; + m12 = matrix[1][2]; + m20 = matrix[2][0]; + m21 = matrix[2][1]; + m22 = matrix[2][2]; + } + + /** + * Recreate Matrix using the provided axis. + * + * @param uAxis Vector3f + * @param vAxis Vector3f + * @param wAxis Vector3f + */ + public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) { + m00 = uAxis.x; + m10 = uAxis.y; + m20 = uAxis.z; + + m01 = vAxis.x; + m11 = vAxis.y; + m21 = vAxis.z; + + m02 = wAxis.x; + m12 = wAxis.y; + m22 = wAxis.z; + } + + /** + * set sets the values of this matrix from an array of values + * assuming that the data is rowMajor order; + * + * @param matrix the matrix to set the value to. + * @throws Exception + */ + public void set(float[] matrix) throws Exception { + set(matrix, true); + } + + /** + * set sets the values of this matrix from an array of values; + * + * @param matrix the matrix to set the value to. + * @param rowMajor whether the incoming data is in row or column major order. + */ + public void set(float[] matrix, boolean rowMajor) throws Exception { + if (matrix.length != 9) + throw new Exception("Array must be of size 9."); + + if (rowMajor) { + m00 = matrix[0]; + m01 = matrix[1]; + m02 = matrix[2]; + m10 = matrix[3]; + m11 = matrix[4]; + m12 = matrix[5]; + m20 = matrix[6]; + m21 = matrix[7]; + m22 = matrix[8]; + } else { + m00 = matrix[0]; + m01 = matrix[3]; + m02 = matrix[6]; + m10 = matrix[1]; + m11 = matrix[4]; + m12 = matrix[7]; + m20 = matrix[2]; + m21 = matrix[5]; + m22 = matrix[8]; + } + } + + /** + * set defines the values of the matrix based on a supplied + * Quaternion. It should be noted that all previous values will + * be overridden. + * + * @param quaternion the quaternion to create a rotational matrix from. + */ + public void set(Quaternion quaternion) { + quaternion.toRotationMatrix(this); + } + + /** + * loadIdentity sets this matrix to the identity matrix. Where + * all values are zero except those along the diagonal which are one. + */ + public void loadIdentity() { + m01 = m02 = m10 = m12 = m20 = m21 = 0; + m00 = m11 = m22 = 1; + } + + /** + * @return true if this matrix is identity + */ + public boolean isIdentity() { + return (m00 == 1 && m01 == 0 && m02 == 0) + && (m10 == 0 && m11 == 1 && m12 == 0) + && (m20 == 0 && m21 == 0 && m22 == 1); + } + + /** + * fromAngleAxis sets this matrix4f to the values specified by + * an angle and an axis of rotation. This method creates an object, so use + * fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + */ + public void fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + } + + /** + * fromAngleNormalAxis sets this matrix4f to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public void fromAngleNormalAxis(float angle, Vector3f axis) { + float fCos = FastMath.cos(angle); + float fSin = FastMath.sin(angle); + float fOneMinusCos = ((float) 1.0) - fCos; + float fX2 = axis.x * axis.x; + float fY2 = axis.y * axis.y; + float fZ2 = axis.z * axis.z; + float fXYM = axis.x * axis.y * fOneMinusCos; + float fXZM = axis.x * axis.z * fOneMinusCos; + float fYZM = axis.y * axis.z * fOneMinusCos; + float fXSin = axis.x * fSin; + float fYSin = axis.y * fSin; + float fZSin = axis.z * fSin; + + m00 = fX2 * fOneMinusCos + fCos; + m01 = fXYM - fZSin; + m02 = fXZM + fYSin; + m10 = fXYM + fZSin; + m11 = fY2 * fOneMinusCos + fCos; + m12 = fYZM - fXSin; + m20 = fXZM - fYSin; + m21 = fYZM + fXSin; + m22 = fZ2 * fOneMinusCos + fCos; + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is returned as a new object. If the given matrix is null, a null + * matrix is returned. + * + * @param mat the matrix to multiply this matrix by. + * @return the result matrix. + */ + public Matrix3f mult(Matrix3f mat) { + return mult(mat, null); + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is returned as a new object. + * + * @param mat the matrix to multiply this matrix by. + * @param product the matrix to store the result in. if null, a new matrix3f is + * created. It is safe for mat and product to be the same object. + * @return a matrix3f object containing the result of this operation + */ + public Matrix3f mult(Matrix3f mat, Matrix3f product) { + + float temp00, temp01, temp02; + float temp10, temp11, temp12; + float temp20, temp21, temp22; + + if (product == null) + product = new Matrix3f(); + temp00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20; + temp01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21; + temp02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22; + temp10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20; + temp11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21; + temp12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22; + temp20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20; + temp21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21; + temp22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22; + + product.m00 = temp00; + product.m01 = temp01; + product.m02 = temp02; + product.m10 = temp10; + product.m11 = temp11; + product.m12 = temp12; + product.m20 = temp20; + product.m21 = temp21; + product.m22 = temp22; + + return product; + } + + /** + * mult multiplies this matrix by a given Vector3f + * object. The result vector is returned. If the given vector is null, null + * will be returned. + * + * @param vec the vector to multiply this matrix by. + * @return the result vector. + */ + public Vector3f mult(Vector3f vec) { + return mult(vec, null); + } + + /** + * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in + * product. + * + * @param vec The Vector3f to multiply. + * @param product The Vector3f to store the result, it is safe for this to be + * the same as vec. + * @return The given product vector. + */ + public Vector3f mult(Vector3f vec, Vector3f product) { + + if (null == product) { + product = new Vector3f(); + } + + float x = vec.x; + float y = vec.y; + float z = vec.z; + + product.x = m00 * x + m01 * y + m02 * z; + product.y = m10 * x + m11 * y + m12 * z; + product.z = m20 * x + m21 * y + m22 * z; + return product; + } + + /** + * multLocal multiplies this matrix internally by a given float + * scale factor. + * + * @param scale the value to scale by. + * @return this Matrix3f + */ + public Matrix3f multLocal(float scale) { + m00 *= scale; + m01 *= scale; + m02 *= scale; + m10 *= scale; + m11 *= scale; + m12 *= scale; + m20 *= scale; + m21 *= scale; + m22 *= scale; + return this; + } + + /** + * multLocal multiplies this matrix by a given + * Vector3f object. The result vector is stored inside the + * passed vector, then returned . If the given vector is null, null will be + * returned. + * + * @param vec the vector to multiply this matrix by. + * @return The passed vector after multiplication + */ + public Vector3f multLocal(Vector3f vec) { + if (vec == null) + return null; + float x = vec.x; + float y = vec.y; + vec.x = m00 * x + m01 * y + m02 * vec.z; + vec.y = m10 * x + m11 * y + m12 * vec.z; + vec.z = m20 * x + m21 * y + m22 * vec.z; + return vec; + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is saved in the current matrix. If the given matrix is null, + * nothing happens. The current matrix is returned. This is equivalent to + * this*=mat + * + * @param mat the matrix to multiply this matrix by. + * @return This matrix, after the multiplication + */ + public Matrix3f multLocal(Matrix3f mat) { + + return mult(mat, this); + } + + /** + * Transposes this matrix in place. Returns this matrix for chaining + * + * @return This matrix after transpose + * @throws Exception + */ + public Matrix3f transposeLocal() throws Exception { + float[] tmp = new float[9]; + get(tmp, false); + set(tmp, true); + return this; + } + + /** + * Inverts this matrix as a new Matrix3f. + * + * @return The new inverse matrix + */ + public Matrix3f invert() { + return invert(null); + } + + /** + * Inverts this matrix and stores it in the given store. + * + * @return The store + */ + public Matrix3f invert(Matrix3f store) { + if (store == null) + store = new Matrix3f(); + + float det = determinant(); + if (FastMath.abs(det) <= 0) + return store.zero(); + + store.m00 = m11 * m22 - m12 * m21; + store.m01 = m02 * m21 - m01 * m22; + store.m02 = m01 * m12 - m02 * m11; + store.m10 = m12 * m20 - m10 * m22; + store.m11 = m00 * m22 - m02 * m20; + store.m12 = m02 * m10 - m00 * m12; + store.m20 = m10 * m21 - m11 * m20; + store.m21 = m01 * m20 - m00 * m21; + store.m22 = m00 * m11 - m01 * m10; + + store.multLocal(1f / det); + return store; + } + + /** + * Inverts this matrix locally. + * + * @return this + */ + public Matrix3f invertLocal() { + float det = determinant(); + if (FastMath.abs(det) <= FastMath.FLT_EPSILON) + return zero(); + + float f00 = m11 * m22 - m12 * m21; + float f01 = m02 * m21 - m01 * m22; + float f02 = m01 * m12 - m02 * m11; + float f10 = m12 * m20 - m10 * m22; + float f11 = m00 * m22 - m02 * m20; + float f12 = m02 * m10 - m00 * m12; + float f20 = m10 * m21 - m11 * m20; + float f21 = m01 * m20 - m00 * m21; + float f22 = m00 * m11 - m01 * m10; + + m00 = f00; + m01 = f01; + m02 = f02; + m10 = f10; + m11 = f11; + m12 = f12; + m20 = f20; + m21 = f21; + m22 = f22; + + multLocal(1f / det); + return this; + } + + /** + * Returns a new matrix representing the adjoint of this matrix. + * + * @return The adjoint matrix + */ + public Matrix3f adjoint() { + return adjoint(null); + } + + /** + * Places the adjoint of this matrix in store (creates store if null.) + * + * @param store The matrix to store the result in. If null, a new matrix is + * created. + * @return store + */ + public Matrix3f adjoint(Matrix3f store) { + if (store == null) + store = new Matrix3f(); + + store.m00 = m11 * m22 - m12 * m21; + store.m01 = m02 * m21 - m01 * m22; + store.m02 = m01 * m12 - m02 * m11; + store.m10 = m12 * m20 - m10 * m22; + store.m11 = m00 * m22 - m02 * m20; + store.m12 = m02 * m10 - m00 * m12; + store.m20 = m10 * m21 - m11 * m20; + store.m21 = m01 * m20 - m00 * m21; + store.m22 = m00 * m11 - m01 * m10; + + return store; + } + + /** + * determinant generates the determinate of this matrix. + * + * @return the determinate + */ + public float determinant() { + float fCo00 = m11 * m22 - m12 * m21; + float fCo10 = m12 * m20 - m10 * m22; + float fCo20 = m10 * m21 - m11 * m20; + return m00 * fCo00 + m01 * fCo10 + m02 * fCo20; + } + + /** + * Sets all of the values in this matrix to zero. + * + * @return this matrix + */ + public Matrix3f zero() { + m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f; + return this; + } + + /** + * add adds the values of a parameter matrix to this matrix. + * + * @param mat the matrix to add to this. + */ + public void add(Matrix3f mat) { + m00 += mat.m00; + m01 += mat.m01; + m02 += mat.m02; + m10 += mat.m10; + m11 += mat.m11; + m12 += mat.m12; + m20 += mat.m20; + m21 += mat.m21; + m22 += mat.m22; + } + + /** + * transpose locally transposes this Matrix. This is + * inconsistent with general value vs local semantics, but is preserved for + * backwards compatibility. Use transposeNew() to transpose to a new object + * (value). + * + * @return this object for chaining. + * @throws Exception + */ + public Matrix3f transpose() throws Exception { + return transposeLocal(); + } + + /** + * transposeNew returns a transposed version of this matrix. + * + * @return The new Matrix3f object. + */ + public Matrix3f transposeNew() { + return new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22); + } + + /** + * toString returns the string representation of this object. + * It is in a format of a 3x3 matrix. For example, an identity matrix would + * be represented by the following string. com.jme.math.Matrix3f
+ * [
+ * 1.0 0.0 0.0
+ * 0.0 1.0 0.0
+ * 0.0 0.0 1.0
+ * ]
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + StringBuffer result = new StringBuffer("com.jme.math.Matrix3f\n[\n"); + result.append(' '); + result.append(m00); + result.append(" "); + result.append(m01); + result.append(" "); + result.append(m02); + result.append(" \n"); + result.append(' '); + result.append(m10); + result.append(" "); + result.append(m11); + result.append(" "); + result.append(m12); + result.append(" \n"); + result.append(' '); + result.append(m20); + result.append(" "); + result.append(m21); + result.append(" "); + result.append(m22); + result.append(" \n]"); + return result.toString(); + } + + /** + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Matrix4f. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(m00); + hash = 37 * hash + Float.floatToIntBits(m01); + hash = 37 * hash + Float.floatToIntBits(m02); + + hash = 37 * hash + Float.floatToIntBits(m10); + hash = 37 * hash + Float.floatToIntBits(m11); + hash = 37 * hash + Float.floatToIntBits(m12); + + hash = 37 * hash + Float.floatToIntBits(m20); + hash = 37 * hash + Float.floatToIntBits(m21); + hash = 37 * hash + Float.floatToIntBits(m22); + + return hash; + } + + /** + * are these two matrices the same? they are is they both have the same mXX + * values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Matrix3f) || o == null) { + return false; + } + + if (this == o) { + return true; + } + + Matrix3f comp = (Matrix3f) o; + if (Float.compare(m00, comp.m00) != 0) + return false; + if (Float.compare(m01, comp.m01) != 0) + return false; + if (Float.compare(m02, comp.m02) != 0) + return false; + + if (Float.compare(m10, comp.m10) != 0) + return false; + if (Float.compare(m11, comp.m11) != 0) + return false; + if (Float.compare(m12, comp.m12) != 0) + return false; + + if (Float.compare(m20, comp.m20) != 0) + return false; + if (Float.compare(m21, comp.m21) != 0) + return false; + return Float.compare(m22, comp.m22) == 0; + } + + /** + * A function for creating a rotation matrix that rotates a vector called + * "start" into another vector called "end". + * + * @param start normalized non-zero starting vector + * @param end normalized non-zero ending vector + * @throws Exception + * @see "Tomas Miller, John Hughes \"Efficiently Building a Matrix to Rotate + * \ One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999" + */ + public void fromStartEndVectors(Vector3f start, Vector3f end) + throws Exception { + Vector3f v = new Vector3f(); + float e, h, f; + + start.cross(end, v); + e = start.dot(end); + f = (e < 0) ? -e : e; + + // if "from" and "to" vectors are nearly parallel + if (f > 1.0f - FastMath.ZERO_TOLERANCE) { + Vector3f u = new Vector3f(); + Vector3f x = new Vector3f(); + float c1, c2, c3; /* coefficients for later use */ + int i, j; + + x.x = (start.x > 0.0) ? start.x : -start.x; + x.y = (start.y > 0.0) ? start.y : -start.y; + x.z = (start.z > 0.0) ? start.z : -start.z; + + if (x.x < x.y) { + if (x.x < x.z) { + x.x = 1.0f; + x.y = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } else { + if (x.y < x.z) { + x.y = 1.0f; + x.x = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } + + u.x = x.x - start.x; + u.y = x.y - start.y; + u.z = x.z - start.z; + v.x = x.x - end.x; + v.y = x.y - end.y; + v.z = x.z - end.z; + + c1 = 2.0f / u.dot(u); + c2 = 2.0f / v.dot(v); + c3 = c1 * c2 * u.dot(v); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + float val = -c1 * u.get(i) * u.get(j) - c2 * v.get(i) + * v.get(j) + c3 * v.get(i) * u.get(j); + set(i, j, val); + } + float val = get(i, i); + set(i, i, val + 1.0f); + } + } else { + // the most common case, unless "start"="end", or "start"=-"end" + float hvx, hvz, hvxy, hvxz, hvyz; + h = 1.0f / (1.0f + e); + hvx = h * v.x; + hvz = h * v.z; + hvxy = hvx * v.y; + hvxz = hvx * v.z; + hvyz = hvz * v.y; + set(0, 0, e + hvx * v.x); + set(0, 1, hvxy - v.z); + set(0, 2, hvxz + v.y); + + set(1, 0, hvxy + v.z); + set(1, 1, e + h * v.y * v.y); + set(1, 2, hvyz - v.x); + + set(2, 0, hvxz - v.y); + set(2, 1, hvyz + v.x); + set(2, 2, e + hvz * v.z); + } + } + + /** + * scale scales the operation performed by this matrix on a + * per-component basis. + * + * @param scale The scale applied to each of the X, Y and Z output values. + */ + public void scale(Vector3f scale) { + m00 *= scale.x; + m10 *= scale.x; + m20 *= scale.x; + m01 *= scale.y; + m11 *= scale.y; + m21 *= scale.y; + m02 *= scale.z; + m12 *= scale.z; + m22 *= scale.z; + } + + @Override + public Matrix3f clone() { + try { + return (Matrix3f) super.clone(); + } catch (CloneNotSupportedException e) { + Logger.error(e); + throw new AssertionError(); // can not happen + } + } +} \ No newline at end of file diff --git a/src/engine/math/Matrix4f.java b/src/engine/math/Matrix4f.java new file mode 100644 index 00000000..197d2b09 --- /dev/null +++ b/src/engine/math/Matrix4f.java @@ -0,0 +1,1710 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import org.pmw.tinylog.Logger; + +import java.nio.FloatBuffer; + + +/** + * Matrix4f defines and maintains a 4x4 matrix in row major order. + * This matrix is intended for use in a translation and rotational capacity. It + * provides convenience methods for creating the matrix from a multitude of + * sources. + *

+ * Matrices are stored assuming column vectors on the right, with the + * translation in the rightmost column. Element numbering is row,column, so m03 + * is the zeroth row, third column, which is the "x" translation part. This + * means that the implicit storage order is column major. However, the get() and + * set() functions on float arrays default to row major order! + */ +public class Matrix4f { + + public float m00, m01, m02, m03; + + public float m10, m11, m12, m13; + + public float m20, m21, m22, m23; + + public float m30, m31, m32, m33; + + /** + * Constructor instantiates a new Matrix that is set to the + * identity matrix. + */ + public Matrix4f() { + loadIdentity(); + } + + /** + * constructs a matrix with the given values. + */ + public Matrix4f(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, + float m22, float m23, float m30, float m31, float m32, float m33) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + /** + * Create a new Matrix4f, given data in column-major format. + * + * @param array An array of 16 floats in column-major format (translation in + * elements 12, 13 and 14). + * @throws Exception + */ + public Matrix4f(float[] array) throws Exception { + set(array, false); + } + + /** + * Constructor instantiates a new Matrix that is set to the + * provided matrix. This constructor copies a given Matrix. If the provided + * matrix is null, the constructor sets the matrix to the identity. + * + * @param mat the matrix to copy. + */ + public Matrix4f(Matrix4f mat) { + copy(mat); + } + + static boolean equalIdentity(Matrix4f mat) { + if (Math.abs(mat.m00 - 1) > 1e-4) + return false; + if (Math.abs(mat.m11 - 1) > 1e-4) + return false; + if (Math.abs(mat.m22 - 1) > 1e-4) + return false; + if (Math.abs(mat.m33 - 1) > 1e-4) + return false; + + if (Math.abs(mat.m01) > 1e-4) + return false; + if (Math.abs(mat.m02) > 1e-4) + return false; + if (Math.abs(mat.m03) > 1e-4) + return false; + + if (Math.abs(mat.m10) > 1e-4) + return false; + if (Math.abs(mat.m12) > 1e-4) + return false; + if (Math.abs(mat.m13) > 1e-4) + return false; + + if (Math.abs(mat.m20) > 1e-4) + return false; + if (Math.abs(mat.m21) > 1e-4) + return false; + if (Math.abs(mat.m23) > 1e-4) + return false; + + if (Math.abs(mat.m30) > 1e-4) + return false; + if (Math.abs(mat.m31) > 1e-4) + return false; + return !(Math.abs(mat.m32) > 1e-4); + } + + /** + * copy transfers the contents of a given matrix to this + * matrix. If a null matrix is supplied, this matrix is set to the identity + * matrix. + * + * @param matrix the matrix to copy. + */ + public void copy(Matrix4f matrix) { + if (null == matrix) { + loadIdentity(); + } else { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m03 = matrix.m03; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m13 = matrix.m13; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + m23 = matrix.m23; + m30 = matrix.m30; + m31 = matrix.m31; + m32 = matrix.m32; + m33 = matrix.m33; + } + } + + /** + * get retrieves the values of this object into a float array + * in row-major order. + * + * @param matrix the matrix to set the values into. + * @throws Exception + */ + public void get(float[] matrix) throws Exception { + get(matrix, true); + } + + /** + * set retrieves the values of this object into a float array. + * + * @param matrix the matrix to set the values into. + * @param rowMajor whether the outgoing data is in row or column major order. + * @throws Exception + */ + public void get(float[] matrix, boolean rowMajor) throws Exception { + if (matrix.length != 16) + throw new Exception("Array must be of size 16."); + + if (rowMajor) { + matrix[0] = m00; + matrix[1] = m01; + matrix[2] = m02; + matrix[3] = m03; + matrix[4] = m10; + matrix[5] = m11; + matrix[6] = m12; + matrix[7] = m13; + matrix[8] = m20; + matrix[9] = m21; + matrix[10] = m22; + matrix[11] = m23; + matrix[12] = m30; + matrix[13] = m31; + matrix[14] = m32; + matrix[15] = m33; + } else { + matrix[0] = m00; + matrix[4] = m01; + matrix[8] = m02; + matrix[12] = m03; + matrix[1] = m10; + matrix[5] = m11; + matrix[9] = m12; + matrix[13] = m13; + matrix[2] = m20; + matrix[6] = m21; + matrix[10] = m22; + matrix[14] = m23; + matrix[3] = m30; + matrix[7] = m31; + matrix[11] = m32; + matrix[15] = m33; + } + } + + /** + * get retrieves a value from the matrix at the given position. + * If the position is invalid a Exception is thrown. + * + * @param i the row index. + * @param j the column index. + * @return the value at (i, j). + * @throws Exception + */ + public float get(int i, int j) throws Exception { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + case 3: + return m03; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + case 3: + return m13; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + case 3: + return m23; + } + case 3: + switch (j) { + case 0: + return m30; + case 1: + return m31; + case 2: + return m32; + case 3: + return m33; + } + } + + throw new Exception("Invalid indices into matrix."); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a float array of length 4. + * + * @param i the column to retrieve. Must be between 0 and 3. + * @return the column specified by the index. + * @throws Exception + */ + public float[] getColumn(int i) throws Exception { + return getColumn(i, null); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a float[4]. + * + * @param i the column to retrieve. Must be between 0 and 3. + * @param store the float array to store the result in. if null, a new one is + * created. + * @return the column specified by the index. + */ + public float[] getColumn(int i, float[] store) throws Exception { + if (store == null) + store = new float[4]; + switch (i) { + case 0: + store[0] = m00; + store[1] = m10; + store[2] = m20; + store[3] = m30; + break; + case 1: + store[0] = m01; + store[1] = m11; + store[2] = m21; + store[3] = m31; + break; + case 2: + store[0] = m02; + store[1] = m12; + store[2] = m22; + store[3] = m32; + break; + case 3: + store[0] = m03; + store[1] = m13; + store[2] = m23; + store[3] = m33; + break; + default: + throw new Exception("Invalid column index. " + i); + } + return store; + } + + /** + * setColumn sets a particular column of this matrix to that + * represented by the provided vector. + * + * @param i the column to set. + * @param column the data to set. + */ + public void setColumn(int i, float[] column) throws Exception { + + if (column == null) { + return; + } + switch (i) { + case 0: + m00 = column[0]; + m10 = column[1]; + m20 = column[2]; + m30 = column[3]; + break; + case 1: + m01 = column[0]; + m11 = column[1]; + m21 = column[2]; + m31 = column[3]; + break; + case 2: + m02 = column[0]; + m12 = column[1]; + m22 = column[2]; + m32 = column[3]; + break; + case 3: + m03 = column[0]; + m13 = column[1]; + m23 = column[2]; + m33 = column[3]; + break; + default: + throw new Exception("Invalid column index. " + i); + } + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a Exception is thrown. + * + * @param i the row index. + * @param j the column index. + * @param value the value for (i, j). + */ + public void set(int i, int j, float value) throws Exception { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return; + case 1: + m01 = value; + return; + case 2: + m02 = value; + return; + case 3: + m03 = value; + return; + } + case 1: + switch (j) { + case 0: + m10 = value; + return; + case 1: + m11 = value; + return; + case 2: + m12 = value; + return; + case 3: + m13 = value; + return; + } + case 2: + switch (j) { + case 0: + m20 = value; + return; + case 1: + m21 = value; + return; + case 2: + m22 = value; + return; + case 3: + m23 = value; + return; + } + case 3: + switch (j) { + case 0: + m30 = value; + return; + case 1: + m31 = value; + return; + case 2: + m32 = value; + return; + case 3: + m33 = value; + return; + } + } + throw new Exception("Invalid indices into matrix."); + } + + /** + * set sets the values of this matrix from an array of values. + * + * @param matrix the matrix to set the value to. + * @throws Exception if the array is not of size 16. + */ + public void set(float[][] matrix) throws Exception { + if (matrix.length != 4 || matrix[0].length != 4) { + throw new Exception("Array must be of size 16."); + } + + m00 = matrix[0][0]; + m01 = matrix[0][1]; + m02 = matrix[0][2]; + m03 = matrix[0][3]; + m10 = matrix[1][0]; + m11 = matrix[1][1]; + m12 = matrix[1][2]; + m13 = matrix[1][3]; + m20 = matrix[2][0]; + m21 = matrix[2][1]; + m22 = matrix[2][2]; + m23 = matrix[2][3]; + m30 = matrix[3][0]; + m31 = matrix[3][1]; + m32 = matrix[3][2]; + m33 = matrix[3][3]; + } + + /** + * set sets the values of this matrix from another matrix. + * + * @param matrix the matrix to read the value from. + */ + public Matrix4f set(Matrix4f matrix) { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m03 = matrix.m03; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m13 = matrix.m13; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + m23 = matrix.m23; + m30 = matrix.m30; + m31 = matrix.m31; + m32 = matrix.m32; + m33 = matrix.m33; + return this; + } + + /** + * set sets the values of this matrix from an array of values + * assuming that the data is rowMajor order; + * + * @param matrix the matrix to set the value to. + * @throws Exception + */ + public void set(float[] matrix) throws Exception { + set(matrix, true); + } + + /** + * set sets the values of this matrix from an array of values; + * + * @param matrix the matrix to set the value to. + * @param rowMajor whether the incoming data is in row or column major order. + * @throws Exception + */ + public void set(float[] matrix, boolean rowMajor) throws Exception { + if (matrix.length != 16) + throw new Exception("Array must be of size 16."); + + if (rowMajor) { + m00 = matrix[0]; + m01 = matrix[1]; + m02 = matrix[2]; + m03 = matrix[3]; + m10 = matrix[4]; + m11 = matrix[5]; + m12 = matrix[6]; + m13 = matrix[7]; + m20 = matrix[8]; + m21 = matrix[9]; + m22 = matrix[10]; + m23 = matrix[11]; + m30 = matrix[12]; + m31 = matrix[13]; + m32 = matrix[14]; + m33 = matrix[15]; + } else { + m00 = matrix[0]; + m01 = matrix[4]; + m02 = matrix[8]; + m03 = matrix[12]; + m10 = matrix[1]; + m11 = matrix[5]; + m12 = matrix[9]; + m13 = matrix[13]; + m20 = matrix[2]; + m21 = matrix[6]; + m22 = matrix[10]; + m23 = matrix[14]; + m30 = matrix[3]; + m31 = matrix[7]; + m32 = matrix[11]; + m33 = matrix[15]; + } + } + + public Matrix4f transpose() throws Exception { + float[] tmp = new float[16]; + get(tmp, true); + return new Matrix4f(tmp); + } + + /** + * transpose locally transposes this Matrix. + * + * @return this object for chaining. + */ + public Matrix4f transposeLocal() { + float tmp = m01; + m01 = m10; + m10 = tmp; + + tmp = m02; + m02 = m20; + m20 = tmp; + + tmp = m03; + m03 = m30; + m30 = tmp; + + tmp = m12; + m12 = m21; + m21 = tmp; + + tmp = m13; + m13 = m31; + m31 = tmp; + + tmp = m23; + m23 = m32; + m32 = tmp; + + return this; + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, must be correct size + * @return matrix data as a FloatBuffer. + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb) { + return fillFloatBuffer(fb, false); + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, starting at current position. Must have + * room for 16 more floats. + * @param columnMajor if true, this buffer should be filled with column major data, + * otherwise it will be filled row major. + * @return matrix data as a FloatBuffer. (position is advanced by 16 and any + * limit set is not changed). + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { + if (columnMajor) { + fb.put(m00).put(m10).put(m20).put(m30); + fb.put(m01).put(m11).put(m21).put(m31); + fb.put(m02).put(m12).put(m22).put(m32); + fb.put(m03).put(m13).put(m23).put(m33); + } else { + fb.put(m00).put(m01).put(m02).put(m03); + fb.put(m10).put(m11).put(m12).put(m13); + fb.put(m20).put(m21).put(m22).put(m23); + fb.put(m30).put(m31).put(m32).put(m33); + } + return fb; + } + + /** + * readFloatBuffer reads value for this matrix from a + * FloatBuffer. + * + * @param fb the buffer to read from, must be correct size + * @return this data as a FloatBuffer. + */ + public Matrix4f readFloatBuffer(FloatBuffer fb) { + return readFloatBuffer(fb, false); + } + + /** + * readFloatBuffer reads value for this matrix from a + * FloatBuffer. + * + * @param fb the buffer to read from, must be correct size + * @param columnMajor if true, this buffer should be filled with column major data, + * otherwise it will be filled row major. + * @return this data as a FloatBuffer. + */ + public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { + + if (columnMajor) { + m00 = fb.get(); + m10 = fb.get(); + m20 = fb.get(); + m30 = fb.get(); + m01 = fb.get(); + m11 = fb.get(); + m21 = fb.get(); + m31 = fb.get(); + m02 = fb.get(); + m12 = fb.get(); + m22 = fb.get(); + m32 = fb.get(); + m03 = fb.get(); + m13 = fb.get(); + m23 = fb.get(); + m33 = fb.get(); + } else { + m00 = fb.get(); + m01 = fb.get(); + m02 = fb.get(); + m03 = fb.get(); + m10 = fb.get(); + m11 = fb.get(); + m12 = fb.get(); + m13 = fb.get(); + m20 = fb.get(); + m21 = fb.get(); + m22 = fb.get(); + m23 = fb.get(); + m30 = fb.get(); + m31 = fb.get(); + m32 = fb.get(); + m33 = fb.get(); + } + return this; + } + + /** + * Legacy wrapper. This name implies that an identity matrix is "loaded", + * but one is not. Instead, the elements of 'this' identity are set. + */ + public void loadIdentity() { + setIdentity(); + } + + /** + * Sets this matrix to the identity matrix, namely all zeros with ones along + * the diagonal. + */ + public void setIdentity() { + m01 = m02 = m03 = m10 = m12 = m13 = m20 = m21 = m23 = m30 = m31 = m32 = 0.0f; + m00 = m11 = m22 = m33 = 1.0f; + } + + /** + * fromAngleAxis sets this matrix4f to the values specified by + * an angle and an axis of rotation. This method creates an object, so use + * fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + */ + public void fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + } + + /** + * fromAngleNormalAxis sets this matrix4f to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public void fromAngleNormalAxis(float angle, Vector3f axis) { + zero(); + m33 = 1; + + float fCos = FastMath.cos(angle); + float fSin = FastMath.sin(angle); + float fOneMinusCos = ((float) 1.0) - fCos; + float fX2 = axis.x * axis.x; + float fY2 = axis.y * axis.y; + float fZ2 = axis.z * axis.z; + float fXYM = axis.x * axis.y * fOneMinusCos; + float fXZM = axis.x * axis.z * fOneMinusCos; + float fYZM = axis.y * axis.z * fOneMinusCos; + float fXSin = axis.x * fSin; + float fYSin = axis.y * fSin; + float fZSin = axis.z * fSin; + + m00 = fX2 * fOneMinusCos + fCos; + m01 = fXYM - fZSin; + m02 = fXZM + fYSin; + m10 = fXYM + fZSin; + m11 = fY2 * fOneMinusCos + fCos; + m12 = fYZM - fXSin; + m20 = fXZM - fYSin; + m21 = fYZM + fXSin; + m22 = fZ2 * fOneMinusCos + fCos; + } + + /** + * mult multiplies this matrix by a scalar. + * + * @param scalar the scalar to multiply this matrix by. + */ + public void multLocal(float scalar) { + m00 *= scalar; + m01 *= scalar; + m02 *= scalar; + m03 *= scalar; + m10 *= scalar; + m11 *= scalar; + m12 *= scalar; + m13 *= scalar; + m20 *= scalar; + m21 *= scalar; + m22 *= scalar; + m23 *= scalar; + m30 *= scalar; + m31 *= scalar; + m32 *= scalar; + m33 *= scalar; + } + + public Matrix4f mult(float scalar) { + Matrix4f out = new Matrix4f(); + out.set(this); + out.multLocal(scalar); + return out; + } + + public Matrix4f mult(float scalar, Matrix4f store) { + store.set(this); + store.multLocal(scalar); + return store; + } + + /** + * mult multiplies this matrix with another matrix. The result + * matrix will then be returned. This matrix will be on the left hand side, + * while the parameter matrix will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @return the resultant matrix + */ + public Matrix4f mult(Matrix4f in2) { + return mult(in2, null); + } + + /** + * mult multiplies this matrix with another matrix. The result + * matrix will then be returned. This matrix will be on the left hand side, + * while the parameter matrix will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @param store where to store the result. It is safe for in2 and store to be + * the same object. + * @return the resultant matrix + */ + public Matrix4f mult(Matrix4f in2, Matrix4f store) { + if (store == null) + store = new Matrix4f(); + + float temp00, temp01, temp02, temp03; + float temp10, temp11, temp12, temp13; + float temp20, temp21, temp22, temp23; + float temp30, temp31, temp32, temp33; + + temp00 = m00 * in2.m00 + m01 * in2.m10 + m02 * in2.m20 + m03 * in2.m30; + temp01 = m00 * in2.m01 + m01 * in2.m11 + m02 * in2.m21 + m03 * in2.m31; + temp02 = m00 * in2.m02 + m01 * in2.m12 + m02 * in2.m22 + m03 * in2.m32; + temp03 = m00 * in2.m03 + m01 * in2.m13 + m02 * in2.m23 + m03 * in2.m33; + + temp10 = m10 * in2.m00 + m11 * in2.m10 + m12 * in2.m20 + m13 * in2.m30; + temp11 = m10 * in2.m01 + m11 * in2.m11 + m12 * in2.m21 + m13 * in2.m31; + temp12 = m10 * in2.m02 + m11 * in2.m12 + m12 * in2.m22 + m13 * in2.m32; + temp13 = m10 * in2.m03 + m11 * in2.m13 + m12 * in2.m23 + m13 * in2.m33; + + temp20 = m20 * in2.m00 + m21 * in2.m10 + m22 * in2.m20 + m23 * in2.m30; + temp21 = m20 * in2.m01 + m21 * in2.m11 + m22 * in2.m21 + m23 * in2.m31; + temp22 = m20 * in2.m02 + m21 * in2.m12 + m22 * in2.m22 + m23 * in2.m32; + temp23 = m20 * in2.m03 + m21 * in2.m13 + m22 * in2.m23 + m23 * in2.m33; + + temp30 = m30 * in2.m00 + m31 * in2.m10 + m32 * in2.m20 + m33 * in2.m30; + temp31 = m30 * in2.m01 + m31 * in2.m11 + m32 * in2.m21 + m33 * in2.m31; + temp32 = m30 * in2.m02 + m31 * in2.m12 + m32 * in2.m22 + m33 * in2.m32; + temp33 = m30 * in2.m03 + m31 * in2.m13 + m32 * in2.m23 + m33 * in2.m33; + + store.m00 = temp00; + store.m01 = temp01; + store.m02 = temp02; + store.m03 = temp03; + store.m10 = temp10; + store.m11 = temp11; + store.m12 = temp12; + store.m13 = temp13; + store.m20 = temp20; + store.m21 = temp21; + store.m22 = temp22; + store.m23 = temp23; + store.m30 = temp30; + store.m31 = temp31; + store.m32 = temp32; + store.m33 = temp33; + + return store; + } + + /** + * mult multiplies this matrix with another matrix. The results + * are stored internally and a handle to this matrix will then be returned. + * This matrix will be on the left hand side, while the parameter matrix + * will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @return the resultant matrix + */ + public Matrix4f multLocal(Matrix4f in2) { + + return mult(in2, this); + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned as a new Vector3f. + * + * @param vec vec to multiply against. + * @return the rotated vector. + */ + public Vector3f mult(Vector3f vec) { + return mult(vec, null); + } + + /** + * mult multiplies a vector about a rotation matrix and adds + * translation. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector3f mult(Vector3f vec, Vector3f store) { + if (store == null) + store = new Vector3f(); + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m01 * vy + m02 * vz + m03; + store.y = m10 * vx + m11 * vy + m12 * vz + m13; + store.z = m20 * vx + m21 * vy + m22 * vz + m23; + + return store; + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. created if null is passed. + * @return the rotated vector. + */ + public Vector3f multAcross(Vector3f vec, Vector3f store) { + if (null == vec) { + return null; + } + if (store == null) + store = new Vector3f(); + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m10 * vy + m20 * vz + m30 * 1; + store.y = m01 * vx + m11 * vy + m21 * vz + m31 * 1; + store.z = m02 * vx + m12 * vy + m22 * vz + m32 * 1; + + return store; + } + + /** + * mult multiplies a quaternion about a matrix. The resulting + * vector is returned. + * + * @param vec vec to multiply against. + * @param store a quaternion to store the result in. created if null is + * passed. + * @return store = this * vec + */ + public Quaternion mult(Quaternion vec, Quaternion store) { + + if (null == vec) { + return null; + } + if (store == null) + store = new Quaternion(); + + float x = m00 * vec.x + m10 * vec.y + m20 * vec.z + m30 * vec.w; + float y = m01 * vec.x + m11 * vec.y + m21 * vec.z + m31 * vec.w; + float z = m02 * vec.x + m12 * vec.y + m22 * vec.z + m32 * vec.w; + float w = m03 * vec.x + m13 * vec.y + m23 * vec.z + m33 * vec.w; + store.x = x; + store.y = y; + store.z = z; + store.w = w; + + return store; + } + + /** + * mult multiplies an array of 4 floats against this rotation + * matrix. The results are stored directly in the array. (vec4f x mat4f) + * + * @param vec4f float array (size 4) to multiply against the matrix. + * @return the vec4f for chaining. + */ + public float[] mult(float[] vec4f) { + if (null == vec4f || vec4f.length != 4) { + return null; + } + + float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3]; + + vec4f[0] = m00 * x + m01 * y + m02 * z + m03 * w; + vec4f[1] = m10 * x + m11 * y + m12 * z + m13 * w; + vec4f[2] = m20 * x + m21 * y + m22 * z + m23 * w; + vec4f[3] = m30 * x + m31 * y + m32 * z + m33 * w; + + return vec4f; + } + + /** + * mult multiplies an array of 4 floats against this rotation + * matrix. The results are stored directly in the array. (vec4f x mat4f) + * + * @param vec4f float array (size 4) to multiply against the matrix. + * @return the vec4f for chaining. + */ + public float[] multAcross(float[] vec4f) { + if (null == vec4f || vec4f.length != 4) { + return null; + } + + float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3]; + + vec4f[0] = m00 * x + m10 * y + m20 * z + m30 * w; + vec4f[1] = m01 * x + m11 * y + m21 * z + m31 * w; + vec4f[2] = m02 * x + m12 * y + m22 * z + m32 * w; + vec4f[3] = m03 * x + m13 * y + m23 * z + m33 * w; + + return vec4f; + } + + /** + * Inverts this matrix as a new Matrix4f. + * + * @return The new inverse matrix + */ + public Matrix4f invert() { + return invert(null); + } + + /** + * Inverts this matrix and stores it in the given store. + * + * @return The store + */ + public Matrix4f invert(Matrix4f store) { + if (store == null) + store = new Matrix4f(); + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + if (FastMath.abs(fDet) <= 0) + return store.zero(); + + store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + float fInvDet = 1.0f / fDet; + store.multLocal(fInvDet); + + return store; + } + + /** + * Inverts this matrix locally. + * + * @return this + */ + public Matrix4f invertLocal() { + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + if (FastMath.abs(fDet) <= FastMath.FLT_EPSILON) + return zero(); + + float f00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + float f10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + float f20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + float f30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + float f01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + float f11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + float f21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + float f31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + float f02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + float f12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + float f22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + float f32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + float f03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + float f13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + float f23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + float f33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + m00 = f00; + m01 = f01; + m02 = f02; + m03 = f03; + m10 = f10; + m11 = f11; + m12 = f12; + m13 = f13; + m20 = f20; + m21 = f21; + m22 = f22; + m23 = f23; + m30 = f30; + m31 = f31; + m32 = f32; + m33 = f33; + + float fInvDet = 1.0f / fDet; + multLocal(fInvDet); + + return this; + } + + /** + * Returns a new matrix representing the adjoint of this matrix. + * + * @return The adjoint matrix + */ + public Matrix4f adjoint() { + return adjoint(null); + } + + /** + * Places the adjoint of this matrix in store (creates store if null.) + * + * @param store The matrix to store the result in. If null, a new matrix is + * created. + * @return store + */ + public Matrix4f adjoint(Matrix4f store) { + if (store == null) + store = new Matrix4f(); + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + + store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + return store; + } + + /** + * determinant generates the determinate of this matrix. + * + * @return the determinate + */ + public float determinant() { + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + return fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + } + + /** + * Sets all of the values in this matrix to zero. + * + * @return this matrix + */ + public Matrix4f zero() { + m00 = m01 = m02 = m03 = m10 = m11 = m12 = m13 = m20 = m21 = m22 = m23 = m30 = m31 = m32 = m33 = 0.0f; + return this; + } + + public Matrix4f add(Matrix4f mat) { + Matrix4f result = new Matrix4f(); + result.m00 = this.m00 + mat.m00; + result.m01 = this.m01 + mat.m01; + result.m02 = this.m02 + mat.m02; + result.m03 = this.m03 + mat.m03; + result.m10 = this.m10 + mat.m10; + result.m11 = this.m11 + mat.m11; + result.m12 = this.m12 + mat.m12; + result.m13 = this.m13 + mat.m13; + result.m20 = this.m20 + mat.m20; + result.m21 = this.m21 + mat.m21; + result.m22 = this.m22 + mat.m22; + result.m23 = this.m23 + mat.m23; + result.m30 = this.m30 + mat.m30; + result.m31 = this.m31 + mat.m31; + result.m32 = this.m32 + mat.m32; + result.m33 = this.m33 + mat.m33; + return result; + } + + /** + * add adds the values of a parameter matrix to this matrix. + * + * @param mat the matrix to add to this. + */ + public void addLocal(Matrix4f mat) { + m00 += mat.m00; + m01 += mat.m01; + m02 += mat.m02; + m03 += mat.m03; + m10 += mat.m10; + m11 += mat.m11; + m12 += mat.m12; + m13 += mat.m13; + m20 += mat.m20; + m21 += mat.m21; + m22 += mat.m22; + m23 += mat.m23; + m30 += mat.m30; + m31 += mat.m31; + m32 += mat.m32; + m33 += mat.m33; + } + + public Vector3f toTranslationVector() { + return new Vector3f(m03, m13, m23); + } + + public void toTranslationVector(Vector3f vector) { + vector.set(m03, m13, m23); + } + + public Quaternion toRotationQuat() { + Quaternion quat = new Quaternion(); + quat.fromRotationMatrix(toRotationMatrix()); + return quat; + } + + public void toRotationQuat(Quaternion q) { + q.fromRotationMatrix(toRotationMatrix()); + } + + public Matrix3f toRotationMatrix() { + return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22); + + } + + public void toRotationMatrix(Matrix3f mat) { + mat.m00 = m00; + mat.m01 = m01; + mat.m02 = m02; + mat.m10 = m10; + mat.m11 = m11; + mat.m12 = m12; + mat.m20 = m20; + mat.m21 = m21; + mat.m22 = m22; + + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param translation the new values for the translation. + * @throws Exception if translation is not size 3. + */ + public void setTranslation(float[] translation) throws Exception { + if (translation.length != 3) { + throw new Exception("Translation size must be 3."); + } + m03 = translation[0]; + m13 = translation[1]; + m23 = translation[2]; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param x value of the translation on the x axis + * @param y value of the translation on the y axis + * @param z value of the translation on the z axis + */ + public void setTranslation(float x, float y, float z) { + m03 = x; + m13 = y; + m23 = z; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param translation the new values for the translation. + */ + public void setTranslation(Vector3f translation) { + m03 = translation.x; + m13 = translation.y; + m23 = translation.z; + } + + /** + * setInverseTranslation will set the matrix's inverse + * translation values. + * + * @param translation the new values for the inverse translation. + * @throws Exception if translation is not size 3. + */ + public void setInverseTranslation(float[] translation) throws Exception { + if (translation.length != 3) { + throw new Exception("Translation size must be 3."); + } + m03 = -translation[0]; + m13 = -translation[1]; + m23 = -translation[2]; + } + + /** + * angleRotation sets this matrix to that of a rotation about + * three axes (x, y, z). Where each axis has a specified rotation in + * degrees. These rotations are expressed in a single Vector3f + * object. + * + * @param angles the angles to rotate. + */ + public void angleRotation(Vector3f angles) { + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = (angles.z * FastMath.DEG_TO_RAD); + sy = FastMath.sin(angle); + cy = FastMath.cos(angle); + angle = (angles.y * FastMath.DEG_TO_RAD); + sp = FastMath.sin(angle); + cp = FastMath.cos(angle); + angle = (angles.x * FastMath.DEG_TO_RAD); + sr = FastMath.sin(angle); + cr = FastMath.cos(angle); + + // matrix = (Z * Y) * X + m00 = cp * cy; + m10 = cp * sy; + m20 = -sp; + m01 = sr * sp * cy + cr * -sy; + m11 = sr * sp * sy + cr * cy; + m21 = sr * cp; + m02 = (cr * sp * cy + -sr * -sy); + m12 = (cr * sp * sy + -sr * cy); + m22 = cr * cp; + m03 = 0.0f; + m13 = 0.0f; + m23 = 0.0f; + } + + /** + * setRotationQuaternion builds a rotation from a + * Quaternion. + * + * @param quat the quaternion to build the rotation from. + * @throws NullPointerException if quat is null. + */ + public void setRotationQuaternion(Quaternion quat) { + quat.toRotationMatrix(this); + } + + /** + * setInverseRotationRadians builds an inverted rotation from + * Euler angles that are in radians. + * + * @param angles the Euler angles in radians. + * @throws Exception if angles is not size 3. + */ + public void setInverseRotationRadians(float[] angles) throws Exception { + if (angles.length != 3) { + throw new Exception("Angles must be of size 3."); + } + double cr = FastMath.cos(angles[0]); + double sr = FastMath.sin(angles[0]); + double cp = FastMath.cos(angles[1]); + double sp = FastMath.sin(angles[1]); + double cy = FastMath.cos(angles[2]); + double sy = FastMath.sin(angles[2]); + + m00 = (float) (cp * cy); + m10 = (float) (cp * sy); + m20 = (float) (-sp); + + double srsp = sr * sp; + double crsp = cr * sp; + + m01 = (float) (srsp * cy - cr * sy); + m11 = (float) (srsp * sy + cr * cy); + m21 = (float) (sr * cp); + + m02 = (float) (crsp * cy + sr * sy); + m12 = (float) (crsp * sy - sr * cy); + m22 = (float) (cr * cp); + } + + /** + * setInverseRotationDegrees builds an inverted rotation from + * Euler angles that are in degrees. + * + * @param angles the Euler angles in degrees. + * @throws Exception if angles is not size 3. + */ + public void setInverseRotationDegrees(float[] angles) throws Exception { + if (angles.length != 3) { + throw new Exception("Angles must be of size 3."); + } + float vec[] = new float[3]; + vec[0] = (angles[0] * FastMath.RAD_TO_DEG); + vec[1] = (angles[1] * FastMath.RAD_TO_DEG); + vec[2] = (angles[2] * FastMath.RAD_TO_DEG); + setInverseRotationRadians(vec); + } + + /** + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param vec the Vector3f data to be translated. + * @throws Exception if the size of the Vector3f is not 3. + */ + public void inverseTranslateVect(float[] vec) throws Exception { + if (vec.length != 3) { + throw new Exception("vec must be of size 3."); + } + + vec[0] -= m03; + vec[1] -= m13; + vec[2] -= m23; + } + + /** + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param data the Vector3f to be translated. + * @throws Exception if the size of the Vector3f is not 3. + */ + public void inverseTranslateVect(Vector3f data) { + data.x -= m03; + data.y -= m13; + data.z -= m23; + } + + /** + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param data the Vector3f to be translated. + * @throws Exception if the size of the Vector3f is not 3. + */ + public void translateVect(Vector3f data) { + data.x += m03; + data.y += m13; + data.z += m23; + } + + /** + * inverseRotateVect rotates a given Vector3f by the rotation + * part of this matrix. + * + * @param vec the Vector3f to be rotated. + */ + public void inverseRotateVect(Vector3f vec) { + float vx = vec.x, vy = vec.y, vz = vec.z; + + vec.x = vx * m00 + vy * m10 + vz * m20; + vec.y = vx * m01 + vy * m11 + vz * m21; + vec.z = vx * m02 + vy * m12 + vz * m22; + } + + public void rotateVect(Vector3f vec) { + float vx = vec.x, vy = vec.y, vz = vec.z; + + vec.x = vx * m00 + vy * m01 + vz * m02; + vec.y = vx * m10 + vy * m11 + vz * m12; + vec.z = vx * m20 + vy * m21 + vz * m22; + } + + /** + * toString returns the string representation of this object. + * It is in a format of a 4x4 matrix. For example, an identity matrix would + * be represented by the following string. com.jme.math.Matrix3f
+ * [
+ * 1.0 0.0 0.0 0.0
+ * 0.0 1.0 0.0 0.0
+ * 0.0 0.0 1.0 0.0
+ * 0.0 0.0 0.0 1.0
+ * ]
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + StringBuffer result = new StringBuffer("com.jme.math.Matrix4f\n[\n"); + result.append(' '); + result.append(m00); + result.append(" "); + result.append(m01); + result.append(" "); + result.append(m02); + result.append(" "); + result.append(m03); + result.append(" \n"); + result.append(' '); + result.append(m10); + result.append(" "); + result.append(m11); + result.append(" "); + result.append(m12); + result.append(" "); + result.append(m13); + result.append(" \n"); + result.append(' '); + result.append(m20); + result.append(" "); + result.append(m21); + result.append(" "); + result.append(m22); + result.append(" "); + result.append(m23); + result.append(" \n"); + result.append(' '); + result.append(m30); + result.append(" "); + result.append(m31); + result.append(" "); + result.append(m32); + result.append(" "); + result.append(m33); + result.append(" \n]"); + return result.toString(); + } + + /** + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Matrix4f. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(m00); + hash = 37 * hash + Float.floatToIntBits(m01); + hash = 37 * hash + Float.floatToIntBits(m02); + hash = 37 * hash + Float.floatToIntBits(m03); + + hash = 37 * hash + Float.floatToIntBits(m10); + hash = 37 * hash + Float.floatToIntBits(m11); + hash = 37 * hash + Float.floatToIntBits(m12); + hash = 37 * hash + Float.floatToIntBits(m13); + + hash = 37 * hash + Float.floatToIntBits(m20); + hash = 37 * hash + Float.floatToIntBits(m21); + hash = 37 * hash + Float.floatToIntBits(m22); + hash = 37 * hash + Float.floatToIntBits(m23); + + hash = 37 * hash + Float.floatToIntBits(m30); + hash = 37 * hash + Float.floatToIntBits(m31); + hash = 37 * hash + Float.floatToIntBits(m32); + hash = 37 * hash + Float.floatToIntBits(m33); + + return hash; + } + + /** + * are these two matrices the same? they are is they both have the same mXX + * values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Matrix4f) || o == null) { + return false; + } + + if (this == o) { + return true; + } + + Matrix4f comp = (Matrix4f) o; + if (Float.compare(m00, comp.m00) != 0) + return false; + if (Float.compare(m01, comp.m01) != 0) + return false; + if (Float.compare(m02, comp.m02) != 0) + return false; + if (Float.compare(m03, comp.m03) != 0) + return false; + + if (Float.compare(m10, comp.m10) != 0) + return false; + if (Float.compare(m11, comp.m11) != 0) + return false; + if (Float.compare(m12, comp.m12) != 0) + return false; + if (Float.compare(m13, comp.m13) != 0) + return false; + + if (Float.compare(m20, comp.m20) != 0) + return false; + if (Float.compare(m21, comp.m21) != 0) + return false; + if (Float.compare(m22, comp.m22) != 0) + return false; + if (Float.compare(m23, comp.m23) != 0) + return false; + + if (Float.compare(m30, comp.m30) != 0) + return false; + if (Float.compare(m31, comp.m31) != 0) + return false; + if (Float.compare(m32, comp.m32) != 0) + return false; + return Float.compare(m33, comp.m33) == 0; + } + + /** + * @return true if this matrix is identity + */ + public boolean isIdentity() { + return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0) && (m10 == 0 && m11 == 1 && m12 == 0 && m13 == 0) + && (m20 == 0 && m21 == 0 && m22 == 1 && m23 == 0) && (m30 == 0 && m31 == 0 && m32 == 0 && m33 == 1); + } + + /** + * Apply a scale to this matrix. + * + * @param scale the scale to apply + */ + public void scale(Vector3f scale) { + m00 *= scale.getX(); + m10 *= scale.getX(); + m20 *= scale.getX(); + m30 *= scale.getX(); + m01 *= scale.getY(); + m11 *= scale.getY(); + m21 *= scale.getY(); + m31 *= scale.getY(); + m02 *= scale.getZ(); + m12 *= scale.getZ(); + m22 *= scale.getZ(); + m32 *= scale.getZ(); + } + + // XXX: This tests more solid than converting the q to a matrix and + // multiplying... why? + public void multLocal(Quaternion rotation) { + Vector3f axis = new Vector3f(); + float angle = rotation.toAngleAxis(axis); + Matrix4f matrix4f = new Matrix4f(); + matrix4f.fromAngleAxis(angle, axis); + multLocal(matrix4f); + } + + @Override + public Matrix4f clone() { + try { + return (Matrix4f) super.clone(); + } catch (CloneNotSupportedException e) { + Logger.error(e); + throw new AssertionError(); // can not happen + } + } +} diff --git a/src/engine/math/Quaternion.java b/src/engine/math/Quaternion.java new file mode 100644 index 00000000..e2f5efa2 --- /dev/null +++ b/src/engine/math/Quaternion.java @@ -0,0 +1,1186 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import org.pmw.tinylog.Logger; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + + +/** + * Quaternion defines a single example of a more general class of + * hypercomplex numbers. Quaternions extends a rotation in three dimensions to a + * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth + * continuous rotation. + * + * Quaternion is defined by four floating point numbers: {x y z w}. + */ + +public class Quaternion { + + private static final Vector3f tmpYaxis = new Vector3f(); + private static final Vector3f tmpZaxis = new Vector3f(); + private static final Vector3f tmpXaxis = new Vector3f(); + public float x, y, z, w; + public float angleX, angleY, angleZ; + + /** + * Constructor instantiates a new Quaternion object + * initializing all values to zero, except w which is initialized to 1. + */ + public Quaternion() { + x = 0; + y = 0; + z = 0; + w = 1; + } + + /** + * Constructor instantiates a new Quaternion object from the + * given list of parameters. + * + * @param x the x value of the quaternion. + * @param y the y value of the quaternion. + * @param z the z value of the quaternion. + * @param w the w value of the quaternion. + */ + public Quaternion(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + float[] angles = new float[3]; + angles = this.toAngles(angles); + angleX = angles[0]; + angleY = angles[1]; + angleZ = angles[2]; + } + + /** + * Constructor instantiates a new Quaternion object from a + * collection of rotation angles. + * + * @param angles the angles of rotation (x, y, z) that will define the + * Quaternion. + */ + public Quaternion(float[] angles) { + fromAngles(angles); + } + + /** + * Constructor instantiates a new Quaternion object from an + * interpolation between two other quaternions. + * + * @param q1 the first quaternion. + * @param q2 the second quaternion. + * @param interp the amount to interpolate between the two quaternions. + */ + public Quaternion(Quaternion q1, Quaternion q2, float interp) { + slerp(q1, q2, interp); + } + + /** + * Constructor instantiates a new Quaternion object from an + * existing quaternion, creating a copy. + * + * @param q the quaternion to copy. + */ + public Quaternion(Quaternion q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + } + + /** + * sets the data in a Quaternion object from the given list of + * parameters. + * + * @param x the x value of the quaternion. + * @param y the y value of the quaternion. + * @param z the z value of the quaternion. + * @param w the w value of the quaternion. + */ + public void set(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /** + * Sets the data in this Quaternion object to be equal to the + * passed Quaternion object. The values are copied producing a + * new object. + * + * @param q The Quaternion to copy values from. + * @return this for chaining + */ + public Quaternion set(Quaternion q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + return this; + } + + /** + * Sets this Quaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1). + */ + public void loadIdentity() { + x = y = z = 0; + w = 1; + } + + /** + * @return true if this Quaternion is {0,0,0,1} + */ + public boolean isIdentity() { + return x == 0 && y == 0 && z == 0 && w == 1; + } + + /** + * fromAngles builds a quaternion from the Euler rotation + * angles (y,r,p). + * + * @param angles the Euler angles of rotation (in radians). + */ + public void fromAngles(float[] angles) { + if (angles.length != 3) + throw new IllegalArgumentException( + "Angles array must have three elements"); + + fromAngles(angles[0], angles[1], angles[2]); + } + + /** + * fromAngles builds a Quaternion from the Euler rotation + * angles (y,r,p). Note that we are applying in order: roll, pitch, yaw but + * we've ordered them in x, y, and z for convenience. See: + * http://www.euclideanspace + * .com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm + * + * @param yaw the Euler yaw of rotation (in radians). (aka Bank, often rot + * around x) + * @param roll the Euler roll of rotation (in radians). (aka Heading, often + * rot around y) + * @param pitch the Euler pitch of rotation (in radians). (aka Attitude, often + * rot around z) + */ + public Quaternion fromAngles(float yaw, float roll, float pitch) { + float angle; + float sinRoll, sinPitch, sinYaw, cosRoll, cosPitch, cosYaw; + angle = pitch * 0.5f; + sinPitch = FastMath.sin(angle); + cosPitch = FastMath.cos(angle); + angle = roll * 0.5f; + sinRoll = FastMath.sin(angle); + cosRoll = FastMath.cos(angle); + angle = yaw * 0.5f; + sinYaw = FastMath.sin(angle); + cosYaw = FastMath.cos(angle); + + // variables used to reduce multiplication calls. + float cosRollXcosPitch = cosRoll * cosPitch; + float sinRollXsinPitch = sinRoll * sinPitch; + float cosRollXsinPitch = cosRoll * sinPitch; + float sinRollXcosPitch = sinRoll * cosPitch; + + w = (cosRollXcosPitch * cosYaw - sinRollXsinPitch * sinYaw); + x = (cosRollXcosPitch * sinYaw + sinRollXsinPitch * cosYaw); + y = (sinRollXcosPitch * cosYaw + cosRollXsinPitch * sinYaw); + z = (cosRollXsinPitch * cosYaw - sinRollXcosPitch * sinYaw); + + normalize(); + return this; + } + + /** + * toAngles returns this quaternion converted to Euler rotation + * angles (yaw,roll,pitch).
+ * See http://www.euclideanspace.com/maths/geometry/rotations/conversions/ + * quaternionToEuler/index.htm + * + * @param angles the float[] in which the angles should be stored, or null if + * you want a new float[] to be created + * @return the float[] in which the angles are stored. + */ + public float[] toAngles(float[] angles) { + if (angles == null) + angles = new float[3]; + else if (angles.length != 3) + throw new IllegalArgumentException( + "Angles array must have three elements"); + + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + angles[1] = 2 * FastMath.atan2(x, w); + angles[2] = FastMath.HALF_PI; + angles[0] = 0; + } else if (test < -0.499 * unit) { // singularity at south pole + angles[1] = -2 * FastMath.atan2(x, w); + angles[2] = -FastMath.HALF_PI; + angles[0] = 0; + } else { + angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + + sqw); // roll or heading + angles[2] = FastMath.asin(2 * test / unit); // pitch or attitude + angles[0] = FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + + sqw); // yaw or bank + } + return angles; + } + + /** + * fromRotationMatrix generates a quaternion from a supplied + * matrix. This matrix is assumed to be a rotational matrix. + * + * @param matrix the matrix that defines the rotation. + */ + public Quaternion fromRotationMatrix(Matrix3f matrix) { + return fromRotationMatrix(matrix.m00, matrix.m01, matrix.m02, + matrix.m10, matrix.m11, matrix.m12, matrix.m20, matrix.m21, + matrix.m22); + } + + public Quaternion fromRotationMatrix(float m00, float m01, float m02, + float m10, float m11, float m12, float m20, float m21, float m22) { + // Use the Graphics Gems code, from + // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z + // *NOT* the "Matrix and Quaternions FAQ", which has errors! + + // the trace is the sum of the diagonal elements; see + // http://mathworld.wolfram.com/MatrixTrace.html + float t = m00 + m11 + m22; + + // we protect the division by s by ensuring that s>=1 + if (t >= 0) { // |w| >= .5 + float s = FastMath.sqrt(t + 1); // |s|>=1 ... + w = 0.5f * s; + s = 0.5f / s; // so this division isn't bad + x = (m21 - m12) * s; + y = (m02 - m20) * s; + z = (m10 - m01) * s; + } else if ((m00 > m11) && (m00 > m22)) { + float s = FastMath.sqrt(1.0f + m00 - m11 - m22); // |s|>=1 + x = s * 0.5f; // |x| >= .5 + s = 0.5f / s; + y = (m10 + m01) * s; + z = (m02 + m20) * s; + w = (m21 - m12) * s; + } else if (m11 > m22) { + float s = FastMath.sqrt(1.0f + m11 - m00 - m22); // |s|>=1 + y = s * 0.5f; // |y| >= .5 + s = 0.5f / s; + x = (m10 + m01) * s; + z = (m21 + m12) * s; + w = (m02 - m20) * s; + } else { + float s = FastMath.sqrt(1.0f + m22 - m00 - m11); // |s|>=1 + z = s * 0.5f; // |z| >= .5 + s = 0.5f / s; + x = (m02 + m20) * s; + y = (m21 + m12) * s; + w = (m10 - m01) * s; + } + + return this; + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. Note: the result is created from a normalized version of this + * quat. + * + * @return the rotation matrix representation of this quaternion. + */ + public Matrix3f toRotationMatrix() { + Matrix3f matrix = new Matrix3f(); + return toRotationMatrix(matrix); + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. The result is stored in result. + * + * @param result The Matrix3f to store the result in. + * @return the rotation matrix representation of this quaternion. + */ + public Matrix3f toRotationMatrix(Matrix3f result) { + + float norm = norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.m00 = 1 - (yy + zz); + result.m01 = (xy - zw); + result.m02 = (xz + yw); + result.m10 = (xy + zw); + result.m11 = 1 - (xx + zz); + result.m12 = (yz - xw); + result.m20 = (xz - yw); + result.m21 = (yz + xw); + result.m22 = 1 - (xx + yy); + + return result; + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. The result is stored in result. 4th row and 4th column values are + * untouched. Note: the result is created from a normalized version of this + * quat. + * + * @param result The Matrix4f to store the result in. + * @return the rotation matrix representation of this quaternion. + */ + public Matrix4f toRotationMatrix(Matrix4f result) { + + float norm = norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.m00 = 1 - (yy + zz); + result.m01 = (xy - zw); + result.m02 = (xz + yw); + result.m10 = (xy + zw); + result.m11 = 1 - (xx + zz); + result.m12 = (yz - xw); + result.m20 = (xz - yw); + result.m21 = (yz + xw); + result.m22 = 1 - (xx + yy); + + return result; + } + + /** + * getRotationColumn returns one of three columns specified by + * the parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @return the column specified by the index. + * @throws Exception + */ + public Vector3f getRotationColumn(int i) throws Exception { + return getRotationColumn(i, null); + } + + /** + * getRotationColumn returns one of three columns specified by + * the parameter. This column is returned as a Vector3f object. + * The value is retrieved as if this quaternion was first normalized. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + * @throws Exception + */ + public Vector3f getRotationColumn(int i, Vector3f store) throws Exception { + if (store == null) + store = new Vector3f(); + + float norm = norm(); + if (norm != 1.0f) { + norm = FastMath.invSqrt(norm); + } + + float xx = x * x * norm; + float xy = x * y * norm; + float xz = x * z * norm; + float xw = x * w * norm; + float yy = y * y * norm; + float yz = y * z * norm; + float yw = y * w * norm; + float zz = z * z * norm; + float zw = z * w * norm; + + switch (i) { + case 0: + store.x = 1 - 2 * (yy + zz); + store.y = 2 * (xy + zw); + store.z = 2 * (xz - yw); + break; + case 1: + store.x = 2 * (xy - zw); + store.y = 1 - 2 * (xx + zz); + store.z = 2 * (yz + xw); + break; + case 2: + store.x = 2 * (xz + yw); + store.y = 2 * (yz - xw); + store.z = 1 - 2 * (xx + yy); + break; + default: + throw new Exception("Invalid column index. " + i); + } + + return store; + } + + /** + * fromAngleAxis sets this quaternion to the values specified + * by an angle and an axis of rotation. This method creates an object, so + * use fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + * @return this quaternion + */ + public Quaternion fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + return this; + } + + /** + * fromAngleNormalAxis sets this quaternion to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) { + if (axis.x == 0 && axis.y == 0 && axis.z == 0) { + loadIdentity(); + } else { + float halfAngle = 0.5f * angle; + float sin = FastMath.sin(halfAngle); + w = FastMath.cos(halfAngle); + x = sin * axis.x; + y = sin * axis.y; + z = sin * axis.z; + } + return this; + } + + /** + * toAngleAxis sets a given angle and axis to that represented + * by the current quaternion. The values are stored as following: The axis + * is provided as a parameter and built by the method, the angle is returned + * as a float. + * + * @param axisStore the object we'll store the computed axis in. + * @return the angle of rotation in radians. + */ + public float toAngleAxis(Vector3f axisStore) { + float sqrLength = x * x + y * y + z * z; + float angle; + if (sqrLength == 0.0f) { + angle = 0.0f; + if (axisStore != null) { + axisStore.x = 1.0f; + axisStore.y = 0.0f; + axisStore.z = 0.0f; + } + } else { + angle = (2.0f * FastMath.acos(w)); + if (axisStore != null) { + float invLength = (1.0f / FastMath.sqrt(sqrLength)); + axisStore.x = x * invLength; + axisStore.y = y * invLength; + axisStore.z = z * invLength; + } + } + + return angle; + } + + /** + * slerp sets this quaternion's value as an interpolation + * between two other quaternions. + * + * @param q1 the first quaternion. + * @param q2 the second quaternion. + * @param t the amount to interpolate between the two quaternions. + */ + public Quaternion slerp(Quaternion q1, Quaternion q2, float t) { + // Create a local quaternion to store the interpolated quaternion + if (q1.x == q2.x && q1.y == q2.y && q1.z == q2.z && q1.w == q2.w) { + this.set(q1); + return this; + } + + float result = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z) + + (q1.w * q2.w); + + if (result < 0.0f) { + // Negate the second quaternion and the result of the dot product + q2.x = -q2.x; + q2.y = -q2.y; + q2.z = -q2.z; + q2.w = -q2.w; + result = -result; + } + + // Set the first and second scale for the interpolation + float scale0 = 1 - t; + float scale1 = t; + + // Check if the angle between the 2 quaternions was big enough to + // warrant such calculations + if ((1 - result) > 0.1f) {// Get the angle between the 2 quaternions, + // and then store the sin() of that angle + float theta = FastMath.acos(result); + float invSinTheta = 1f / FastMath.sin(theta); + + // Calculate the scale for q1 and q2, according to the angle and + // it's sine value + scale0 = FastMath.sin((1 - t) * theta) * invSinTheta; + scale1 = FastMath.sin((t * theta)) * invSinTheta; + } + + // Calculate the x, y, z and w values for the quaternion by using a + // special + // form of linear interpolation for quaternions. + this.x = (scale0 * q1.x) + (scale1 * q2.x); + this.y = (scale0 * q1.y) + (scale1 * q2.y); + this.z = (scale0 * q1.z) + (scale1 * q2.z); + this.w = (scale0 * q1.w) + (scale1 * q2.w); + + // Return the interpolated quaternion + return this; + } + + /** + * Sets the values of this quaternion to the slerp from itself to q2 by + * changeAmnt + * + * @param q2 Final interpolation value + * @param changeAmnt The amount difference + */ + public void slerp(Quaternion q2, float changeAmnt) { + if (this.x == q2.x && this.y == q2.y && this.z == q2.z + && this.w == q2.w) { + return; + } + + float result = (this.x * q2.x) + (this.y * q2.y) + (this.z * q2.z) + + (this.w * q2.w); + + if (result < 0.0f) { + // Negate the second quaternion and the result of the dot product + q2.x = -q2.x; + q2.y = -q2.y; + q2.z = -q2.z; + q2.w = -q2.w; + result = -result; + } + + // Set the first and second scale for the interpolation + float scale0 = 1 - changeAmnt; + float scale1 = changeAmnt; + + // Check if the angle between the 2 quaternions was big enough to + // warrant such calculations + if ((1 - result) > 0.1f) { + // Get the angle between the 2 quaternions, and then store the sin() + // of that angle + float theta = FastMath.acos(result); + float invSinTheta = 1f / FastMath.sin(theta); + + // Calculate the scale for q1 and q2, according to the angle and + // it's sine value + scale0 = FastMath.sin((1 - changeAmnt) * theta) * invSinTheta; + scale1 = FastMath.sin((changeAmnt * theta)) * invSinTheta; + } + + // Calculate the x, y, z and w values for the quaternion by using a + // special + // form of linear interpolation for quaternions. + this.x = (scale0 * this.x) + (scale1 * q2.x); + this.y = (scale0 * this.y) + (scale1 * q2.y); + this.z = (scale0 * this.z) + (scale1 * q2.z); + this.w = (scale0 * this.w) + (scale1 * q2.w); + } + + /** + * add adds the values of this quaternion to those of the + * parameter quaternion. The result is returned as a new quaternion. + * + * @param q the quaternion to add to this. + * @return the new quaternion. + */ + public Quaternion add(Quaternion q) { + return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w); + } + + /** + * add adds the values of this quaternion to those of the + * parameter quaternion. The result is stored in this Quaternion. + * + * @param q the quaternion to add to this. + * @return This Quaternion after addition. + */ + public Quaternion addLocal(Quaternion q) { + this.x += q.x; + this.y += q.y; + this.z += q.z; + this.w += q.w; + return this; + } + + /** + * subtract subtracts the values of the parameter quaternion + * from those of this quaternion. The result is returned as a new + * quaternion. + * + * @param q the quaternion to subtract from this. + * @return the new quaternion. + */ + public Quaternion subtract(Quaternion q) { + return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w); + } + + /** + * subtract subtracts the values of the parameter quaternion + * from those of this quaternion. The result is stored in this Quaternion. + * + * @param q the quaternion to subtract from this. + * @return This Quaternion after subtraction. + */ + public Quaternion subtractLocal(Quaternion q) { + this.x -= q.x; + this.y -= q.y; + this.z -= q.z; + this.w -= q.w; + return this; + } + + /** + * mult multiplies this quaternion by a parameter quaternion. + * The result is returned as a new quaternion. It should be noted that + * quaternion multiplication is not cummulative so q * p != p * q. + * + * @param q the quaternion to multiply this quaternion by. + * @return the new quaternion. + */ + public Quaternion mult(Quaternion q) { + return mult(q, null); + } + + /** + * mult multiplies this quaternion by a parameter quaternion + * (q). 'this' is not modified. It should be noted that quaternion + * multiplication is not cummulative so q * p != p * q. + *

+ * It IS safe for q and res to be the same object. + * + * @param q the quaternion to multiply this quaternion by. + * @param res the quaternion to store the result in (may be null). If + * non-null, the input values of 'res' will be ignored and + * replaced. + * @return If specified res is null, then a new Quaternion; otherwise + * returns the populated 'res'. + */ + public Quaternion mult(Quaternion q, Quaternion res) { + if (res == null) + res = new Quaternion(); + float qw = q.w, qx = q.x, qy = q.y, qz = q.z; + res.x = x * qw + y * qz - z * qy + w * qx; + res.y = -x * qz + y * qw + z * qx + w * qy; + res.z = x * qy - y * qx + z * qw + w * qz; + res.w = -x * qx - y * qy - z * qz + w * qw; + + float[] angles = new float[3]; + angles = res.toAngles(angles); + res.angleX = angles[0]; + res.angleY = angles[1]; + res.angleZ = angles[2]; + return res; + } + + /** + * apply multiplies this quaternion by a parameter matrix + * internally. + * + * @param matrix the matrix to apply to this quaternion. + */ + public void apply(Matrix3f matrix) { + float oldX = x, oldY = y, oldZ = z, oldW = w; + fromRotationMatrix(matrix); + float tempX = x, tempY = y, tempZ = z, tempW = w; + + x = oldX * tempW + oldY * tempZ - oldZ * tempY + oldW * tempX; + y = -oldX * tempZ + oldY * tempW + oldZ * tempX + oldW * tempY; + z = oldX * tempY - oldY * tempX + oldZ * tempW + oldW * tempZ; + w = -oldX * tempX - oldY * tempY - oldZ * tempZ + oldW * tempW; + } + + /** + * fromAxes creates a Quaternion that represents + * the coordinate system defined by three axes. These axes are assumed to be + * orthogonal and no error checking is applied. Thus, the user must insure + * that the three axes being provided indeed represents a proper right + * handed coordinate system. + * + * @param axis the array containing the three vectors representing the + * coordinate system. + */ + public Quaternion fromAxes(Vector3f[] axis) { + if (axis.length != 3) + throw new IllegalArgumentException( + "Axis array must have three elements"); + return fromAxes(axis[0], axis[1], axis[2]); + } + + /** + * fromAxes creates a Quaternion that represents + * the coordinate system defined by three axes. These axes are assumed to be + * orthogonal and no error checking is applied. Thus, the user must insure + * that the three axes being provided indeed represents a proper right + * handed coordinate system. + * + * @param xAxis vector representing the x-axis of the coordinate system. + * @param yAxis vector representing the y-axis of the coordinate system. + * @param zAxis vector representing the z-axis of the coordinate system. + */ + public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) { + return fromRotationMatrix(xAxis.x, yAxis.x, zAxis.x, xAxis.y, yAxis.y, + zAxis.y, xAxis.z, yAxis.z, zAxis.z); + } + + /** + * toAxes takes in an array of three vectors. Each vector + * corresponds to an axis of the coordinate system defined by the quaternion + * rotation. + * + * @param axis the array of vectors to be filled. + * @throws Exception + */ + public void toAxes(Vector3f axis[]) throws Exception { + Matrix3f tempMat = toRotationMatrix(); + axis[0] = tempMat.getColumn(0, axis[0]); + axis[1] = tempMat.getColumn(1, axis[1]); + axis[2] = tempMat.getColumn(2, axis[2]); + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. 'this' is not modified. + * + * @param v the vector to multiply this quaternion by. + * @return the new vector. + */ + public Vector3f mult(Vector3f v) { + return mult(v, null); + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is stored in the supplied vector This method is very poorly named, + * since the specified vector is modified and, contrary to the other *Local + * methods in this and other jME classes, 'this' remains unchanged. + * + * @param v the vector which this Quaternion multiplies. + * @return v + */ + public Vector3f multLocal(Vector3f v) { + float tempX, tempY; + tempX = w * w * v.x + 2 * y * w * v.z - 2 * z * w * v.y + x * x * v.x + + 2 * y * x * v.y + 2 * z * x * v.z - z * z * v.x - y * y * v.x; + tempY = 2 * x * y * v.x + y * y * v.y + 2 * z * y * v.z + 2 * w * z + * v.x - z * z * v.y + w * w * v.y - 2 * x * w * v.z - x * x + * v.y; + v.z = 2 * x * z * v.x + 2 * y * z * v.y + z * z * v.z - 2 * w * y * v.x + - y * y * v.z + 2 * w * x * v.y - x * x * v.z + w * w * v.z; + v.x = tempX; + v.y = tempY; + return v; + } + + /** + * Multiplies this Quaternion by the supplied quaternion. The result is + * stored in this Quaternion, which is also returned for chaining. Similar + * to this *= q. + * + * @param q The Quaternion to multiply this one by. + * @return This Quaternion, after multiplication. + */ + public Quaternion multLocal(Quaternion q) { + float x1 = x * q.w + y * q.z - z * q.y + w * q.x; + float y1 = -x * q.z + y * q.w + z * q.x + w * q.y; + float z1 = x * q.y - y * q.x + z * q.w + w * q.z; + w = -x * q.x - y * q.y - z * q.z + w * q.w; + x = x1; + y = y1; + z = z1; + return this; + } + + /** + * Multiplies this Quaternion by the supplied quaternion. The result is + * stored in this Quaternion, which is also returned for chaining. Similar + * to this *= q. + * + * @param qx - quat x value + * @param qy - quat y value + * @param qz - quat z value + * @param qw - quat w value + * @return This Quaternion, after multiplication. + */ + public Quaternion multLocal(float qx, float qy, float qz, float qw) { + float x1 = x * qw + y * qz - z * qy + w * qx; + float y1 = -x * qz + y * qw + z * qx + w * qy; + float z1 = x * qy - y * qx + z * qw + w * qz; + w = -x * qx - y * qy - z * qz + w * qw; + x = x1; + y = y1; + z = z1; + return this; + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. 'this' is not modified. + * + * @param v the vector to multiply this quaternion by. + * @param store the vector to store the result in. It IS safe for v and store + * to be the same object. + * @return the result vector. + */ + public Vector3f mult(Vector3f v, Vector3f store) { + if (store == null) + store = new Vector3f(); + if (v.x == 0 && v.y == 0 && v.z == 0) { + store.set(0, 0, 0); + } else { + float vx = v.x, vy = v.y, vz = v.z; + store.x = w * w * vx + 2 * y * w * vz - 2 * z * w * vy + x * x * vx + + 2 * y * x * vy + 2 * z * x * vz - z * z * vx - y * y * vx; + store.y = 2 * x * y * vx + y * y * vy + 2 * z * y * vz + 2 * w * z + * vx - z * z * vy + w * w * vy - 2 * x * w * vz - x * x + * vy; + store.z = 2 * x * z * vx + 2 * y * z * vy + z * z * vz - 2 * w * y + * vx - y * y * vz + 2 * w * x * vy - x * x * vz + w * w + * vz; + } + return store; + } + + /** + * mult multiplies this quaternion by a parameter scalar. The + * result is returned as a new quaternion. + * + * @param scalar the quaternion to multiply this quaternion by. + * @return the new quaternion. + */ + public Quaternion mult(float scalar) { + return new Quaternion(scalar * x, scalar * y, scalar * z, scalar * w); + } + + /** + * mult multiplies this quaternion by a parameter scalar. The + * result is stored locally. + * + * @param scalar the quaternion to multiply this quaternion by. + * @return this. + */ + public Quaternion multLocal(float scalar) { + w *= scalar; + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * dot calculates and returns the dot product of this + * quaternion with that of the parameter quaternion. + * + * @param q the quaternion to calculate the dot product of. + * @return the dot product of this and the parameter quaternion. + */ + public float dot(Quaternion q) { + return w * q.w + x * q.x + y * q.y + z * q.z; + } + + /** + * norm returns the norm of this quaternion. This is the dot + * product of this quaternion with itself. + * + * @return the norm of the quaternion. + */ + public float norm() { + return w * w + x * x + y * y + z * z; + } + + /** + * normalize normalizes the current Quaternion + */ + public void normalize() { + float n = FastMath.invSqrt(norm()); + x *= n; + y *= n; + z *= n; + w *= n; + } + + /** + * inverse returns the inverse of this quaternion as a new + * quaternion. If this quaternion does not have an inverse (if its normal is + * 0 or less), then null is returned. + * + * @return the inverse of this quaternion or null if the inverse does not + * exist. + */ + public Quaternion inverse() { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + return new Quaternion(-x * invNorm, -y * invNorm, -z * invNorm, w + * invNorm); + } + // return an invalid result to flag the error + return null; + } + + /** + * inverse calculates the inverse of this quaternion and + * returns this quaternion after it is calculated. If this quaternion does + * not have an inverse (if it's norma is 0 or less), nothing happens + * + * @return the inverse of this quaternion + */ + public Quaternion inverseLocal() { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + x *= -invNorm; + y *= -invNorm; + z *= -invNorm; + w *= invNorm; + } + return this; + } + + /** + * negate inverts the values of the quaternion. + */ + public void negate() { + x *= -1; + y *= -1; + z *= -1; + w *= -1; + } + + /** + * equals determines if two quaternions are logically equal, + * that is, if the values of (x, y, z, w) are the same for both quaternions. + * + * @param o the object to compare for equality + * @return true if they are equal, false otherwise. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Quaternion)) { + return false; + } + + if (this == o) { + return true; + } + + Quaternion comp = (Quaternion) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + if (Float.compare(z, comp.z) != 0) + return false; + return Float.compare(w, comp.w) == 0; + } + + /** + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Quaternion. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(x); + hash = 37 * hash + Float.floatToIntBits(y); + hash = 37 * hash + Float.floatToIntBits(z); + hash = 37 * hash + Float.floatToIntBits(w); + return hash; + + } + + /** + * readExternal builds a quaternion from an + * ObjectInput object.
+ * NOTE: Used with serialization. Not to be called manually. + * + * @param in the ObjectInput value to read from. + * @throws IOException if the ObjectInput value has problems reading a float. + * @see java.io.Externalizable + */ + public void readExternal(ObjectInput in) throws IOException { + x = in.readFloat(); + y = in.readFloat(); + z = in.readFloat(); + w = in.readFloat(); + } + + /** + * writeExternal writes this quaternion out to a + * ObjectOutput object. NOTE: Used with serialization. Not to + * be called manually. + * + * @param out the object to write to. + * @throws IOException if writing to the ObjectOutput fails. + * @see java.io.Externalizable + */ + public void writeExternal(ObjectOutput out) throws IOException { + out.writeFloat(x); + out.writeFloat(y); + out.writeFloat(z); + out.writeFloat(w); + } + + /** + * lookAt is a convienence method for auto-setting the + * quaternion based on a direction and an up vector. It computes the + * rotation to transform the z-axis to point into 'direction' and the y-axis + * to 'up'. + * + * @param direction where to look at in terms of local coordinates + * @param up a vector indicating the local up direction. (typically {0, 1, + * 0} in jME.) + */ + public void lookAt(Vector3f direction, Vector3f up) { + tmpZaxis.set(direction).normalizeLocal(); + tmpXaxis.set(up).crossLocal(direction).normalizeLocal(); + tmpYaxis.set(direction).crossLocal(tmpXaxis).normalizeLocal(); + fromAxes(tmpXaxis, tmpYaxis, tmpZaxis); + } + + /** + * @return A new quaternion that describes a rotation that would point you + * in the exact opposite direction of this Quaternion. + */ + public Quaternion opposite() { + return opposite(null); + } + + /** + * @param store A Quaternion to store our result in. If null, a new one is + * created. + * @return The store quaternion (or a new Quaterion, if store is null) that + * describes a rotation that would point you in the exact opposite + * direction of this Quaternion. + */ + public Quaternion opposite(Quaternion store) { + if (store == null) + store = new Quaternion(); + + Vector3f axis = new Vector3f(); + float angle = toAngleAxis(axis); + + store.fromAngleAxis(FastMath.PI + angle, axis); + return store; + } + + /** + * @return This Quaternion, altered to describe a rotation that would point + * you in the exact opposite direction of where it is pointing + * currently. + */ + public Quaternion oppositeLocal() { + return opposite(this); + } + + @Override + public Quaternion clone() { + try { + return (Quaternion) super.clone(); + } catch (CloneNotSupportedException e) { + Logger.error(e); + throw new AssertionError(); // can not happen + } + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public float getZ() { + return z; + } + + public void setZ(float z) { + this.z = z; + } + + public float getW() { + return w; + } + + public void setW(float w) { + this.w = w; + } +} diff --git a/src/engine/math/Vector2f.java b/src/engine/math/Vector2f.java new file mode 100644 index 00000000..d69e593e --- /dev/null +++ b/src/engine/math/Vector2f.java @@ -0,0 +1,621 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +/** + * Vector2f defines a Vector for a two float value vector. + */ +public class Vector2f { + + /** + * the x value of the vector. + */ + public float x; + /** + * the y value of the vector. + */ + public float y; + + /** + * Creates a Vector2f with the given initial x and y values. + * + * @param x The x value of this Vector2f. + * @param y The y value of this Vector2f. + */ + public Vector2f(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0). + */ + public Vector2f() { + x = y = 0; + } + + /** + * Creates a new Vector2f that contains the passed vector's information + * + * @param vector2f The vector to copy + */ + public Vector2f(Vector2f vector2f) { + this.x = vector2f.x; + this.y = vector2f.y; + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector2f vector) { + if (vector == null) + return false; + if (Float.isNaN(vector.x) || Float.isNaN(vector.y)) + return false; + return !Float.isInfinite(vector.x) && !Float.isInfinite(vector.y); + } + + public static boolean isZeroVector(Vector2f vector) { + + return (vector.x == 0) && + (vector.y == 0); + + } + + /** + * set the x and y values of the vector + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @return this vector + */ + public Vector2f set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + /** + * set the x and y values of the vector from another vector + * + * @param vec the vector to copy from + * @return this vector + */ + public Vector2f set(Vector2f vec) { + this.x = vec.x; + this.y = vec.y; + return this; + } + + /** + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector2f add(Vector2f vec) { + if (null == vec) { + return null; + } + return new Vector2f(x + vec.x, y + vec.y); + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector2f addLocal(Vector2f vec) { + if (null == vec) { + return null; + } + x += vec.x; + y += vec.y; + return this; + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @return this + */ + public Vector2f addLocal(float addX, float addY) { + x += addX; + y += addY; + return this; + } + + /** + * add adds this vector by vec and stores the + * result in result. + * + * @param vec The vector to add. + * @param result The vector to store the result in. + * @return The result vector, after adding. + */ + public Vector2f add(Vector2f vec, Vector2f result) { + if (null == vec) { + return null; + } + if (result == null) + result = new Vector2f(); + result.x = x + vec.x; + result.y = y + vec.y; + return result; + } + + /** + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector2f vec) { + if (null == vec) { + return 0; + } + return x * vec.x + y * vec.y; + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return the cross product vector. + */ + public Vector3f cross(Vector2f v) { + return new Vector3f(0, 0, determinant(v)); + } + + public float determinant(Vector2f v) { + return (x * v.y) - (y * v.x); + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage change + * from this towards finalVec + */ + public void interpolate(Vector2f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec The begining vector (delta=0) + * @param finalVec The final vector to interpolate towards (delta=1) + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage change + * from beginVec towards finalVec + */ + public void interpolate(Vector2f beginVec, Vector2f finalVec, + float changeAmnt) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector2f v) { + double dx = x - v.x; + double dy = y - v.y; + return (float) (dx * dx + dy * dy); + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @return the distance squared between the two vectors. + */ + public float distanceSquared(float otherX, float otherY) { + double dx = x - otherX; + double dy = y - otherY; + return (float) (dx * dx + dy * dy); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector2f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + /** + * mult multiplies this vector by a scalar. The resultant + * vector is returned. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector2f mult(float scalar) { + return new Vector2f(x * scalar, y * scalar); + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector2f multLocal(float scalar) { + x *= scalar; + y *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector2f multLocal(Vector2f vec) { + if (null == vec) { + return null; + } + x *= vec.x; + y *= vec.y; + return this; + } + + /** + * Multiplies this Vector2f's x and y by the scalar and stores the result in + * product. The result is returned for chaining. Similar to + * product=this*scalar; + * + * @param scalar The scalar to multiply by. + * @param product The vector2f to store the result in. + * @return product, after multiplication. + */ + public Vector2f mult(float scalar, Vector2f product) { + if (null == product) { + product = new Vector2f(); + } + + product.x = x * scalar; + product.y = y * scalar; + return product; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector2f divide(float scalar) { + return new Vector2f(x / scalar, y / scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector2f divideLocal(float scalar) { + x /= scalar; + y /= scalar; + return this; + } + + /** + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector2f negate() { + return new Vector2f(-x, -y); + } + + /** + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector2f negateLocal() { + x = -x; + y = -y; + return this; + } + + /** + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, an exception is thrown. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector2f subtract(Vector2f vec) { + return subtract(vec, null); + } + + /** + * subtract subtracts the values of a given vector from those + * of this vector storing the result in the given vector object. If the + * provided vector is null, an exception is thrown. + * + * @param vec the vector to subtract from this vector. + * @param store the vector to store the result in. It is safe for this to be + * the same as vec. If null, a new vector is created. + * @return the result vector. + */ + public Vector2f subtract(Vector2f vec, Vector2f store) { + if (store == null) + store = new Vector2f(); + store.x = x - vec.x; + store.y = y - vec.y; + return store; + } + + /** + * subtract subtracts the given x,y values from those of this + * vector creating a new vector object. + * + * @param valX value to subtract from x + * @param valY value to subtract from y + * @return this + */ + public Vector2f subtract(float valX, float valY) { + return new Vector2f(x - valX, y - valY); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector2f subtractLocal(Vector2f vec) { + if (null == vec) { + return null; + } + x -= vec.x; + y -= vec.y; + return this; + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param valX value to subtract from x + * @param valY value to subtract from y + * @return this + */ + public Vector2f subtractLocal(float valX, float valY) { + x -= valX; + y -= valY; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector2f normalize() { + float length = length(); + if (length != 0) { + return divide(length); + } + + return divide(1); + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this. + */ + public Vector2f normalizeLocal() { + float length = length(); + if (length != 0) { + return divideLocal(length); + } + + return divideLocal(1); + } + + /** + * smallestAngleBetween returns (in radians) the minimum angle + * between two vectors. It is assumed that both this vector and the given + * vector are unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float smallestAngleBetween(Vector2f otherVector) { + float dotProduct = dot(otherVector); + return FastMath.acos(dotProduct); + } + + /** + * angleBetween returns (in radians) the angle required to + * rotate a ray represented by this vector to lie colinear to a ray + * described by the given vector. It is assumed that both this vector and + * the given vector are unit vectors (iow, normalized). + * + * @param otherVector the "destination" unit vector + * @return the angle in radians. + */ + public float angleBetween(Vector2f otherVector) { + return FastMath.atan2(otherVector.y, otherVector.x) + - FastMath.atan2(y, x); + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + /** + * getAngle returns (in radians) the angle represented by this + * Vector2f as expressed by a conversion from rectangular coordinates ( + * x, y) to polar coordinates + * (r, theta). + * + * @return the angle in radians. [-pi, pi) + */ + public float getAngle() { + return -FastMath.atan2(y, x); + } + + /** + * zero resets this vector's data to zero internally. + */ + public void zero() { + x = y = 0; + } + + @Override + public Vector2f clone() { + try { + return (Vector2f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector2f into the given float[] object. + * + * @param floats The float[] to take this Vector2f. If null, a new float[2] is + * created. + * @return The array, with X, Y float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[2]; + } + floats[0] = x; + floats[1] = y; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x and + * y values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector2f)) { + return false; + } + + if (this == o) { + return true; + } + + Vector2f comp = (Vector2f) o; + if (Float.compare(x, comp.x) != 0) + return false; + return Float.compare(y, comp.y) == 0; + } + + public void rotateAroundOrigin(float angle, boolean cw) { + if (cw) + angle = -angle; + float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y; + float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y; + x = newX; + y = newY; + } + + public synchronized float getLat() { + return x; + } + + public synchronized void setLat(float lat) { + this.x = lat; + } + + public synchronized float getLong() { + return y; + } + + public synchronized void setLong(float lon) { + this.y = lon; + } + +} diff --git a/src/engine/math/Vector3f.java b/src/engine/math/Vector3f.java new file mode 100644 index 00000000..98af001c --- /dev/null +++ b/src/engine/math/Vector3f.java @@ -0,0 +1,1092 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + + +/** + * Vector3f defines a Vector for a three float value tuple. + * Vector3f can represent any three dimensional value, such as a + * vertex, a normal, etc. Utility methods are also included to aid in + * mathematical calculations. + */ + +public class Vector3f { + public final static Vector3f ZERO = new Vector3f(0, 0, 0); + + public final static Vector3f UNIT_X = new Vector3f(1, 0, 0); + public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0); + public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1); + public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); + + /** + * the x value of the vector. + */ + public float x; + + /** + * the y value of the vector. + */ + public float y; + + /** + * the z value of the vector. + */ + public float z; + + /** + * Constructor instantiates a new Vector3f with default values + * of (0,0,0). + */ + public Vector3f() { + x = y = z = 0.0f; + } + + /** + * Constructor instantiates a new Vector3f with provides + * values. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + */ + public Vector3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3f(Vector3fImmutable original) { + this.x = original.x; + this.y = original.y; + this.z = original.z; + } + + /** + * Constructor instantiates a new Vector3f that is a copy of + * the provided vector + * + * @param copy The Vector3f to copy + */ + public Vector3f(Vector3f copy) { + this.set(copy); + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector3f vector) { + if (vector == null) + return false; + if (Float.isNaN(vector.x) || Float.isNaN(vector.y) || Float.isNaN(vector.z)) + return false; + return !Float.isInfinite(vector.x) && !Float.isInfinite(vector.y) && !Float.isInfinite(vector.z); + } + + public static void generateOrthonormalBasis(Vector3f u, Vector3f v, Vector3f w) { + w.normalizeLocal(); + generateComplementBasis(u, v, w); + } + + public static void generateComplementBasis(Vector3f u, Vector3f v, Vector3f w) { + float fInvLength; + + if (FastMath.abs(w.x) >= FastMath.abs(w.y)) { + // w.x or w.z is the largest magnitude component, swap them + fInvLength = FastMath.invSqrt(w.x * w.x + w.z * w.z); + u.x = -w.z * fInvLength; + u.y = 0.0f; + u.z = +w.x * fInvLength; + v.x = w.y * u.z; + v.y = w.z * u.x - w.x * u.z; + v.z = -w.y * u.x; + } else { + // w.y or w.z is the largest magnitude component, swap them + fInvLength = FastMath.invSqrt(w.y * w.y + w.z * w.z); + u.x = 0.0f; + u.y = +w.z * fInvLength; + u.z = -w.y * fInvLength; + v.x = w.y * u.z - w.z * u.y; + v.y = -w.x * u.z; + v.z = w.x * u.y; + } + } + + /** + * Returns the 2D face direction from rotation. + * + * @param rotation Rotation in radians + */ + public static Vector3f getFaceDir(float rotation) { + return new Vector3f(FastMath.sin(rotation), 0f, FastMath.cos(rotation)); + } + + /** + * Returns the 2D cross direction (perpendicular face direction) from rotation. + * + * @param rotation Rotation in radians + */ + public static Vector3f getCrossDir(float rotation) { + return new Vector3f(FastMath.cos(rotation), 0f, FastMath.sin(rotation)); + } + + public static Vector3f rotateAroundPoint(Vector3f origin, Vector3f point, double angle) { + + float angleRadians; + double modifiedAngle; + + // Convert angle to radians + + modifiedAngle = angle; + + if (angle < 0) + modifiedAngle = 360 + modifiedAngle; + + angleRadians = (float) Math.toRadians(modifiedAngle); + + return rotateAroundPoint(origin, point, angleRadians); + } + + public static Vector3f rotateAroundPoint(Vector3f origin, Vector3f point, float radians) { + + Vector3f outVector; + Vector3f directionVector; + Quaternion angleRotation; + + // Build direction vector relative to origin + + directionVector = new Vector3f(point.subtract(origin)); + + // Build quaternion rotation + + angleRotation = new Quaternion().fromAngleAxis(radians, new Vector3f(0, 1, 0)); + + // Apply rotation to direction vector + + directionVector = angleRotation.mult(directionVector); + + // Translate from origin back to new rotated point + + outVector = origin.add(directionVector); + + return outVector; + + } + + public static Vector3f min(Vector3f vectorA, Vector3f vectorB) { + + return new Vector3f(Math.min(vectorA.x, vectorB.x), + Math.min(vectorA.y, vectorB.y), + Math.min(vectorA.z, vectorB.z)); + } + + public static Vector3f max(Vector3f vectorA, Vector3f vectorB) { + + return new Vector3f(Math.max(vectorA.x, vectorB.x), + Math.max(vectorA.y, vectorB.y), + Math.max(vectorA.z, vectorB.z)); + } + + public static Vector3f rotateAroundPoint(Vector3f origin, Vector3f point, Quaternion angleRotation) { + + Vector3f outVector; + Vector3f directionVector; + // Build direction vector relative to origin + directionVector = new Vector3f(point.subtract(origin)); + directionVector = angleRotation.mult(directionVector); + + // Translate from origin back to new rotated point + + outVector = origin.add(directionVector); + + return outVector; + + } + + /** + * set sets the x,y,z values of the vector based on passed + * parameters. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @return this vector + */ + public Vector3f set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + /** + * set sets the x,y,z values of the vector by copying the + * supplied vector. + * + * @param vect the vector to copy. + * @return this vector + */ + public Vector3f set(Vector3f vect) { + this.x = vect.x; + this.y = vect.y; + this.z = vect.z; + return this; + } + + /** + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + *

+ * Neither 'this' nor 'vec' are modified. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector3f add(Vector3f vec) { + if (null == vec) { + return null; + } + return new Vector3f(x + vec.x, y + vec.y, z + vec.z); + } + + /** + * add adds the values of a provided vector storing the values + * in the supplied vector. + * + * @param vec the vector to add to this + * @param result the vector to store the result in + * @return result returns the supplied result vector. + */ + public Vector3f add(Vector3f vec, Vector3f result) { + result.x = x + vec.x; + result.y = y + vec.y; + result.z = z + vec.z; + return result; + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector3f addLocal(Vector3f vec) { + if (null == vec) { + return null; + } + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + /** + * add adds the provided values to this vector, creating a new + * vector that is then returned. + * + * @param addX the x value to add. + * @param addY the y value to add. + * @param addZ the z value to add. + * @return the result vector. + */ + public Vector3f add(float addX, float addY, float addZ) { + return new Vector3f(x + addX, y + addY, z + addZ); + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @param addZ value to add to z + * @return this + */ + public Vector3f addLocal(float addX, float addY, float addZ) { + x += addX; + y += addY; + z += addZ; + return this; + } + + /** + * scaleAdd multiplies this vector by a scalar then adds the + * given Vector3f. + * + * @param scalar the value to multiply this vector by. + * @param add the value to add + */ + public void scaleAdd(float scalar, Vector3f add) { + x = x * scalar + add.x; + y = y * scalar + add.y; + z = z * scalar + add.z; + } + + /** + * scaleAdd multiplies the given vector by a scalar then adds + * the given vector. + * + * @param scalar the value to multiply this vector by. + * @param mult the value to multiply the scalar by + * @param add the value to add + */ + public void scaleAdd(float scalar, Vector3f mult, Vector3f add) { + this.x = mult.x * scalar + add.x; + this.y = mult.y * scalar + add.y; + this.z = mult.z * scalar + add.z; + } + + /** + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector3f vec) { + if (null == vec) { + return 0; + } + return x * vec.x + y * vec.y + z * vec.z; + } + + /** + * Returns a new vector which is the cross product of this vector with the + * specified vector. + *

+ * Neither 'this' nor v are modified. The starting value of 'result' + *

+ * + * @param v the vector to take the cross product of with this. + * @return the cross product vector. + */ + public Vector3f cross(Vector3f v) { + return cross(v, null); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + *

+ * Neither 'this' nor v are modified. The starting value of 'result' (if + * any) is ignored. + *

+ * + * @param v the vector to take the cross product of with this. + * @param result the vector to store the cross product result. + * @return result, after receiving the cross product vector. + */ + public Vector3f cross(Vector3f v, Vector3f result) { + return cross(v.x, v.y, v.z, result); + } + + /** + * cross calculates the cross product of this vector with a + * Vector comprised of the specified other* elements. The result is stored + * in result, without modifying either 'this' or the 'other*' + * values. + * + * @param otherX x component of the vector to take the cross product of with + * this. + * @param otherY y component of the vector to take the cross product of with + * this. + * @param otherZ z component of the vector to take the cross product of with + * this. + * @param result the vector to store the cross product result. + * @return result, after receiving the cross product vector. + */ + public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) { + if (result == null) + result = new Vector3f(); + float resX = ((y * otherZ) - (z * otherY)); + float resY = ((z * otherX) - (x * otherZ)); + float resZ = ((x * otherY) - (y * otherX)); + result.set(resX, resY, resZ); + return result; + } + + /** + * crossLocal calculates the cross product of this vector with + * a parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return this. + */ + public Vector3f crossLocal(Vector3f v) { + return crossLocal(v.x, v.y, v.z); + } + + /** + * crossLocal calculates the cross product of this vector with + * a parameter vector v. + * + * @param otherX x component of the vector to take the cross product of with + * this. + * @param otherY y component of the vector to take the cross product of with + * this. + * @param otherZ z component of the vector to take the cross product of with + * this. + * @return this. + */ + public Vector3f crossLocal(float otherX, float otherY, float otherZ) { + float tempx = (y * otherZ) - (z * otherY); + float tempy = (z * otherX) - (x * otherZ); + z = (x * otherY) - (y * otherX); + x = tempx; + y = tempy; + return this; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y + z * z; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector3f v) { + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + return (float) (dx * dx + dy * dy + dz * dz); + } + + public float distanceSquared2D(Vector3f v) { + double dx = x - v.x; + double dz = z - v.z; + return (float) (dx * dx + dz * dz); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector3f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + public float distance2D(Vector3f v) { + return FastMath.sqrt(distanceSquared2D(v)); + } + + /** + * mult multiplies this vector by a scalar. The resultant + * vector is returned. "this" is not modified. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector3f mult(float scalar) { + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + /** + * mult multiplies this vector by a scalar. The resultant + * vector is supplied as the second parameter and returned. "this" is not + * modified. + * + * @param scalar the scalar to multiply this vector by. + * @param product the product to store the result in. + * @return product + */ + public Vector3f mult(float scalar, Vector3f product) { + if (null == product) { + product = new Vector3f(); + } + + product.x = x * scalar; + product.y = y * scalar; + product.z = z * scalar; + return product; + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector3f multLocal(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. The provided + * 'vec' is not modified. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector3f multLocal(Vector3f vec) { + if (null == vec) { + return null; + } + x *= vec.x; + y *= vec.y; + z *= vec.z; + return this; + } + + /** + * Returns a new Vector instance comprised of elements which are the product + * of the corresponding vector elements. (N.b. this is not a cross product). + *

+ * Neither 'this' nor 'vec' are modified. + *

+ * + * @param vec the vector to mult to this vector. + */ + public Vector3f mult(Vector3f vec) { + if (null == vec) { + return null; + } + return mult(vec, null); + } + + /** + * Multiplies a provided 'vec' vector with this vector. If the specified + * 'store' is null, then a new Vector instance is returned. Otherwise, + * 'store' with replaced values will be returned, to facilitate chaining. + *

+ *

+ * 'This' is not modified; and the starting value of 'store' (if any) is + * ignored (and over-written). + *

+ * The resultant Vector is comprised of elements which are the product of + * the corresponding vector elements. (N.b. this is not a cross product). + *

+ * + * @param vec the vector to mult to this vector. + * @param store result vector (null to create a new vector) + * @return 'store', or a new Vector3f + */ + public Vector3f mult(Vector3f vec, Vector3f store) { + if (null == vec) { + return null; + } + if (store == null) + store = new Vector3f(); + return store.set(x * vec.x, y * vec.y, z * vec.z); + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector3f divide(float scalar) { + scalar = 1f / scalar; + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector3f divideLocal(float scalar) { + scalar = 1f / scalar; + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector3f divide(Vector3f scalar) { + return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector3f divideLocal(Vector3f scalar) { + x /= scalar.x; + y /= scalar.y; + z /= scalar.z; + return this; + } + + /** + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector3f negate() { + return new Vector3f(-x, -y, -z); + } + + /** + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector3f negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + /** + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, null is returned. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector3f subtract(Vector3f vec) { + return new Vector3f(x - vec.x, y - vec.y, z - vec.z); + } + + public Vector3f subtract2D(Vector3f vec) { + return new Vector3f(x - vec.x, 0, z - vec.z); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector3f subtractLocal(Vector3f vec) { + if (null == vec) { + return null; + } + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + /** + * subtract + * + * @param vec the vector to subtract from this + * @param result the vector to store the result in + * @return result + */ + public Vector3f subtract(Vector3f vec, Vector3f result) { + if (result == null) { + result = new Vector3f(); + } + result.x = x - vec.x; + result.y = y - vec.y; + result.z = z - vec.z; + return result; + } + + /** + * subtract subtracts the provided values from this vector, + * creating a new vector that is then returned. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @return the result vector. + */ + public Vector3f subtract(float subtractX, float subtractY, float subtractZ) { + return new Vector3f(x - subtractX, y - subtractY, z - subtractZ); + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @return this + */ + public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) { + x -= subtractX; + y -= subtractY; + z -= subtractZ; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector3f normalize() { + float length = length(); + if (length != 0) { + return divide(length); + } + + return divide(1); + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this. + */ + public Vector3f normalizeLocal() { + float length = length(); + if (length != 0) { + return divideLocal(length); + } + + return this; + } + + /** + * zero resets this vector's data to zero internally. + */ + public void zero() { + x = y = z = 0; + } + + /** + * angleBetween returns (in radians) the angle between two + * vectors. It is assumed that both this vector and the given vector are + * unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float angleBetween(Vector3f otherVector) { + float dotProduct = dot(otherVector); + return FastMath.acos(dotProduct); + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage change + * from this towards finalVec + */ + public void interpolate(Vector3f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; + } + + public Vector3f lerp(Vector3f finalVec, float changeAmnt) { + float x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + float y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + float z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; + return new Vector3f(x, y, z); + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec the beginning vector (changeAmnt=0) + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage change + * from beginVec towards finalVec + */ + public void interpolate(Vector3f beginVec, Vector3f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z; + } + + @Override + public Vector3f clone() { + try { + return (Vector3f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector3f into the given float[] object. + * + * @param floats The float[] to take this Vector3f. If null, a new float[3] is + * created. + * @return The array, with X, Y, Z float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[3]; + } + floats[0] = x; + floats[1] = y; + floats[2] = z; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x,y, + * and z values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector3f)) { + return false; + } + + if (this == o) { + return true; + } + + Vector3f comp = (Vector3f) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + return Float.compare(z, comp.z) == 0; + } + + /** + * hashCode returns a unique code for this vector object based + * on it's values. If two vectors are logically equivalent, they will return + * the same hash code value. + * + * @return the hash code value of this vector. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + hash += 37 * hash + Float.floatToIntBits(z); + return hash; + } + + /** + * Used with serialization. Not to be called manually. + * + * @param in input + * @throws IOException + * @throws ClassNotFoundException + * @see java.io.Externalizable + */ + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + x = in.readFloat(); + y = in.readFloat(); + z = in.readFloat(); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out output + * @throws IOException + * @see java.io.Externalizable + */ + public void writeExternal(ObjectOutput out) throws IOException { + out.writeFloat(x); + out.writeFloat(y); + out.writeFloat(z); + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public float getZ() { + return z; + } + + public void setZ(float z) { + this.z = z; + } + + /** + * @param index + * @return x value if index == 0, y value if index == 1 or z value if index + * == 2 + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public float get(int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + /** + * @param index which field index in this vector to set. + * @param value to set to one of x, y or z. + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public void set(int index, float value) { + switch (index) { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + /** + * Gets an offset from this position based on rotation around Y(up/down)-axis. + * + * @param rotation Rotation in radians + * @param xOffset Amount to offset along x axis (left negative, right positive) + * @param yOffset Amount to offset along y axis (down negative, up positive) + * @param zOffset Amount to offset along z axis (backwards negative, forwards positive) + * @param invertZ whether to invert the z axis + */ + public Vector3f getOffset(float rotation, float xOffset, float yOffset, float zOffset, boolean invertZ) { + float sin = FastMath.sin(rotation); + float cos = FastMath.cos(rotation); + Vector3f faceDir = new Vector3f(sin, 0f, cos); + Vector3f crossDir = new Vector3f(cos, 0f, sin); + faceDir.multLocal(zOffset); + crossDir.multLocal(xOffset); + if (invertZ) { + faceDir.z = -faceDir.z; + crossDir.z = -crossDir.z; + } + Vector3f loc = new Vector3f(this); + loc.addLocal(faceDir); + loc.addLocal(crossDir); + loc.y += yOffset; + return loc; + } + + /** + * Returns the 2D rotation (around Y-axis) in radians. + * + * @return + */ + public float getRotation() { + return 3.14f + FastMath.atan2(-x, -z); + } + + /** + * Gets the XYZ component of this Vector3f + * + * @return + */ + public Vector2f getLatLong() { + return new Vector2f(this.x, this.z); + } + + public synchronized float getLat() { + return x; + } + + public synchronized void setLat(float lat) { + this.x = lat; + } + + public synchronized float getLong() { + return z; + } + + public synchronized void setLong(float lon) { + this.z = lon; + } + + public synchronized float getAlt() { + return y; + } + + public synchronized void setAlt(float alt) { + this.y = alt; + } + + @Override + public String toString() { + String out = ""; + out += "x=" + x + ", "; + out += "y=" + y + ", "; + out += "z=" + z; + return out; + } + +} diff --git a/src/engine/math/Vector3fImmutable.java b/src/engine/math/Vector3fImmutable.java new file mode 100644 index 00000000..4c5bf4ad --- /dev/null +++ b/src/engine/math/Vector3fImmutable.java @@ -0,0 +1,592 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.math; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; + +public class Vector3fImmutable { + + public static final Vector3fImmutable ZERO = new Vector3fImmutable(0, 0, 0); + public final float x, y, z; + + public Vector3fImmutable() { + x = y = z = 0.0f; + } + + public Vector3fImmutable(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3fImmutable(Vector3f original) { + this.x = original.x; + this.y = original.y; + this.z = original.z; + } + + public Vector3fImmutable(Vector3fImmutable original) { + this.x = original.x; + this.y = original.y; + this.z = original.z; + } + + public static Vector3fImmutable scaleAdd(float scalar, Vector3fImmutable mult, + Vector3fImmutable add) { + return new Vector3fImmutable(mult.x * scalar + add.x, mult.y * scalar + + add.y, mult.z * scalar + add.z); + } + + public static boolean isValidVector(Vector3fImmutable vector) { + if (vector == null) + return false; + if (Float.isNaN(vector.x) || Float.isNaN(vector.y) + || Float.isNaN(vector.z)) + return false; + return !Float.isInfinite(vector.x) && !Float.isInfinite(vector.y) + && !Float.isInfinite(vector.z); + } + + public static Vector3fImmutable readExternal(ObjectInput in) + throws IOException, ClassNotFoundException { + return new Vector3fImmutable(in.readFloat(), in.readFloat(), in + .readFloat()); + } + + public static String toString(Vector3fImmutable vector) { + + return vector.toString(); + } + + public static Vector3fImmutable ClosestPointOnLine(Vector3fImmutable lineStart, Vector3fImmutable lineEnd, Vector3fImmutable sourcePoint) { + + Vector3fImmutable closestPoint; + Vector3fImmutable lineStartToTarget; + Vector3fImmutable lineDirection; + float lineLength; + float dotProduct; + + lineStartToTarget = sourcePoint.subtract(lineStart); + lineDirection = lineEnd.subtract(lineStart).normalize(); + lineLength = lineStart.distance2D(lineEnd); + + dotProduct = lineDirection.dot(lineStartToTarget); + + if (dotProduct <= 0) + return lineStart; + + if (dotProduct >= lineLength) + return lineEnd; + + // Project the point by advancing it along the line from + // the starting point. + + closestPoint = lineDirection.mult(dotProduct); + closestPoint = lineStart.add(closestPoint); + + return closestPoint; + } + + public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point, int angle) { + + float angleRadians; + int modifiedAngle; + + // Convert angle to radians + + modifiedAngle = angle; + + if (angle < 0) + modifiedAngle = 360 + modifiedAngle; + + angleRadians = (float) Math.toRadians(modifiedAngle); + + return rotateAroundPoint(origin, point, angleRadians); + } + + public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point, float radians) { + + Vector3fImmutable outVector; + Vector3f directionVector; + Quaternion angleRotation; + + // Build direction vector relative to origin + + directionVector = new Vector3f(point.subtract(origin)); + + // Build quaternion rotation + + angleRotation = new Quaternion().fromAngleAxis(radians, new Vector3f(0, 1, 0)); + // Apply rotation to direction vector + + directionVector = angleRotation.mult(directionVector); + + // Translate from origin back to new rotated point + + outVector = origin.add(directionVector); + + return outVector; + + } + + public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point, Quaternion angleRotation) { + + Vector3fImmutable outVector; + Vector3f directionVector; + // Build direction vector relative to origin + directionVector = new Vector3f(point.subtract(origin)); + + // Build quaternion rotation + + + // Apply rotation to direction vector + + + directionVector = angleRotation.mult(directionVector); + + // Translate from origin back to new rotated point + + outVector = origin.add(directionVector); + + return outVector; + + } + + public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point, float w, Vector3f axis) { + + Vector3fImmutable outVector; + Vector3f directionVector; + Quaternion angleRotation; + + // Build direction vector relative to origin + + directionVector = new Vector3f(point.subtract(origin)); + + // Build quaternion rotation + + angleRotation = new Quaternion().fromAngleAxis(w, axis); + // Apply rotation to direction vector + + directionVector = angleRotation.mult(directionVector); + + // Translate from origin back to new rotated point + + outVector = origin.add(directionVector); + + return outVector; + + } + + public static Vector3fImmutable getRandomPointInCircle(Vector3fImmutable origin, float radius) { + // Member variables + + float targetAngle; + float targetRadius; + Vector3fImmutable targetPosition; + + targetAngle = (float) (ThreadLocalRandom.current().nextFloat() * Math.PI * 2); + targetRadius = (float) (Math.sqrt(ThreadLocalRandom.current().nextFloat()) * radius); + targetPosition = new Vector3fImmutable((float) (origin.x + targetRadius * Math.cos(targetAngle)), origin.y, (float) (origin.z + targetRadius * Math.sin(targetAngle))); + return targetPosition; + } + + public static Vector3fImmutable getLocBetween(Vector3fImmutable start, Vector3fImmutable end) { + // Member variables + + Vector3fImmutable faceDirection = end.subtract(start).normalize(); + float distance = end.distance(start) * .5f; + return faceDirection.scaleAdd(distance, start); + } + + public static Vector3fImmutable getRandomPointOnCircle(Vector3fImmutable origin, float radius) { + + // Member variables + + int randomAngle; + Vector3fImmutable targetPosition; + + randomAngle = ThreadLocalRandom.current().nextInt(360); + + targetPosition = new Vector3fImmutable((float) (origin.x + radius * Math.cos(randomAngle)), origin.y, (float) (origin.z + radius * Math.sin(randomAngle))); + return targetPosition; + } + + public static Vector3fImmutable transform(Vector3fImmutable origin, Vector3fImmutable point, float angle) { + + //TRANSLATE TO ORIGIN + float x1 = point.x - origin.x; + float y1 = point.z - origin.z; + + //APPLY ROTATION + float temp_x1 = (float) (x1 * Math.cos(angle) - y1 * Math.sin(angle)); + float temp_z1 = (float) (x1 * Math.sin(angle) + y1 * Math.cos(angle)); + + temp_x1 += origin.x; + temp_z1 += origin.z; + + return new Vector3fImmutable(temp_x1, point.y, temp_z1); + } + + public boolean isInsideCircle(Vector3fImmutable circleCenter, float radius) { + + return (circleCenter.distanceSquared2D(this) < sqr(radius)); + } + + public Vector3fImmutable add(Vector3f vec) { + if (null == vec) + return null; + return new Vector3fImmutable(x + vec.x, y + vec.y, z + vec.z); + } + + public Vector3fImmutable add(Vector3fImmutable vec) { + if (null == vec) + return null; + return new Vector3fImmutable(x + vec.x, y + vec.y, z + vec.z); + } + + public Vector3fImmutable add(float x, float y, float z) { + return new Vector3fImmutable(this.x + x, this.y + y, this.z + z); + } + + public Vector3fImmutable scaleAdd(float scalar, Vector3fImmutable add) { + return new Vector3fImmutable(x * scalar + add.x, y * scalar + add.y, z + * scalar + add.z); + } + + public float dot(Vector3fImmutable vec) { + if (null == vec) { + return 0.0f; + } + + return x * vec.x + y * vec.y + z * vec.z; + } + + public float dot2D(Vector3fImmutable vec) { + if (null == vec) { + return 0.0f; + } + + return x * vec.x + z * vec.z; + } + + public Vector3fImmutable cross(Vector3fImmutable v) { + return cross(v.x, v.y, v.z); + } + + public Vector3fImmutable cross(float x, float y, float z) { + return new Vector3fImmutable(this.y * z - this.z * y, this.z * x + - this.x * z, this.x * y - this.y * x); + } + + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + public float lengthSquared() { + return x * x + y * y + z * z; + } + + public float distanceSquared(Vector3fImmutable v) { + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + return (float) (dx * dx + dy * dy + dz * dz); + } + + public float magnitude() { + return FastMath.sqrt(sqrMagnitude()); + } + + public float sqrMagnitude() { + return x * x + y * y + z * z; + } + + public Vector3fImmutable moveTowards(Vector3fImmutable target, float maxDistanceDelta) { + Vector3fImmutable outVector; + + Vector3fImmutable direction = target.subtract2D(this); + float magnitude = direction.magnitude(); + + if (magnitude <= maxDistanceDelta || magnitude == 0f) { + return target; + } + + outVector = direction.divide(magnitude).mult(maxDistanceDelta); + outVector = this.add(outVector); + return outVector; + } + + public float distanceSquared2D(Vector3fImmutable v) { + double dx = x - v.x; + double dz = z - v.z; + return (float) (dx * dx + dz * dz); + } + + public float distance(Vector3fImmutable v) { + return FastMath.sqrt(distanceSquared(v)); + } + + public float distance2D(Vector3fImmutable v) { + return FastMath.sqrt(distanceSquared2D(v)); + } + + public Vector3fImmutable mult(float scalar) { + return new Vector3fImmutable(x * scalar, y * scalar, z * scalar); + } + + public Vector3fImmutable mult(Vector3fImmutable vec) { + if (null == vec) { + return null; + } + + return new Vector3fImmutable(x * vec.x, y * vec.y, z * vec.z); + } + + public Vector3fImmutable divide(float scalar) { + scalar = 1f / scalar; + return new Vector3fImmutable(x * scalar, y * scalar, z * scalar); + } + + public Vector3fImmutable divide(Vector3fImmutable scalar) { + return new Vector3fImmutable(x / scalar.x, y / scalar.y, z / scalar.z); + } + + public Vector3fImmutable negate() { + return new Vector3fImmutable(-x, -y, -z); + } + + public Vector3fImmutable subtract(Vector3fImmutable vec) { + return new Vector3fImmutable(x - vec.x, y - vec.y, z - vec.z); + } + + public Vector3fImmutable subtract2D(Vector3fImmutable vec) { + return new Vector3fImmutable(x - vec.x, 0, z - vec.z); + } + + public Vector3fImmutable subtract(float x, float y, float z) { + return new Vector3fImmutable(this.x - x, this.y - y, this.z - z); + } + + public Vector3fImmutable normalize() { + float length = length(); + if (length != 0) { + return divide(length); + } + + return divide(1); + } + + public float angleBetween(Vector3fImmutable otherVector) { + float dotProduct = dot(otherVector); + return FastMath.acos(dotProduct); + } + + public float angleBetween2D(Vector3fImmutable otherVector) { + float dotProduct = dot(otherVector); + return FastMath.acos(dotProduct); + } + + public Vector3fImmutable interpolate(Vector3f finalVec, float changeAmnt) { + return new Vector3fImmutable((1 - changeAmnt) * this.x + changeAmnt + * finalVec.x, (1 - changeAmnt) * this.y + changeAmnt + * finalVec.y, (1 - changeAmnt) * this.z + changeAmnt + * finalVec.z); + } + + public Vector3fImmutable interpolate(Vector3fImmutable finalVec, float changeAmnt) { + return new Vector3fImmutable((1 - changeAmnt) * this.x + changeAmnt + * finalVec.x, (1 - changeAmnt) * this.y + changeAmnt + * finalVec.y, (1 - changeAmnt) * this.z + changeAmnt + * finalVec.z); + } + + @Override + public Vector3fImmutable clone() throws CloneNotSupportedException { + return (Vector3fImmutable) super.clone(); + } + + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[3]; + } + floats[0] = x; + floats[1] = y; + floats[2] = z; + return floats; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector3fImmutable)) { + return false; + } + + if (this == o) { + return true; + } + + Vector3fImmutable comp = (Vector3fImmutable) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + return Float.compare(z, comp.z) == 0; + } + + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + hash += 37 * hash + Float.floatToIntBits(z); + return hash; + } + + public void writeExternal(ObjectOutput out) throws IOException { + out.writeFloat(x); + out.writeFloat(y); + out.writeFloat(z); + } + + public Vector3fImmutable getOffset(float rotation, float xOffset, float yOffset, float zOffset, boolean invertZ) { + float sin = FastMath.sin(rotation); + float cos = FastMath.cos(rotation); + Vector3f faceDir = new Vector3f(sin, 0f, cos); + Vector3f crossDir = new Vector3f(cos, 0f, sin); + faceDir.multLocal(zOffset); + crossDir.multLocal(xOffset); + if (invertZ) { + faceDir.z = -faceDir.z; + crossDir.z = -crossDir.z; + } + Vector3f loc = new Vector3f(this); + loc.addLocal(faceDir); + loc.addLocal(crossDir); + loc.y += yOffset; + return new Vector3fImmutable(loc); + } + + public float getX() { + return x; + } + + public Vector3fImmutable setX(float x) { + return new Vector3fImmutable(x, y, z); + } + + public float getY() { + return y; + } + + public Vector3fImmutable setY(float y) { + return new Vector3fImmutable(x, y, z); + } + + public float getZ() { + return z; + } + + public Vector3fImmutable setZ(float z) { + return new Vector3fImmutable(x, y, z); + } + + public float get(int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + public Vector2f getLatLong() { + return new Vector2f(this.x, this.z); + } + + public synchronized float getLat() { + return x; + } + + public synchronized float getLong() { + return z; + } + + public synchronized float getAlt() { + return y; + } + + public float getRotation() { + return 3.14f + FastMath.atan2(-x, -z); + } + + public boolean inRange2D(Vector3fImmutable otherVec, float range) { + float distance = this.distanceSquared2D(otherVec); + return !(distance > range * range); + } + + @Override + public String toString() { + + String outString; + + outString = "(" + this.x + '/' + this.y + '/' + this.z; + return outString; + + } + + public String toString2D() { + + String outString; + + outString = "( " + (int) this.x + " , " + (int) (this.z * -1) + " )"; + return outString; + + } + + public Vector3fImmutable ClosestPointOnLine(Vector3fImmutable lineStart, Vector3fImmutable lineEnd) { + + Vector3fImmutable closestPoint; + Vector3fImmutable lineStartToTarget; + Vector3fImmutable lineDirection; + float lineLength; + float dotProduct; + + lineStartToTarget = this.subtract(lineStart); + lineDirection = lineEnd.subtract(lineStart).normalize(); + lineLength = lineStart.distance2D(lineEnd); + + dotProduct = lineDirection.dot(lineStartToTarget); + + if (dotProduct <= 0) + return lineStart; + + if (dotProduct >= lineLength) + return lineEnd; + + // Project the point by advancing it along the line from + // the starting point. + + closestPoint = lineDirection.mult(dotProduct); + closestPoint = lineStart.add(closestPoint); + + return closestPoint; + } + + public float Lerp(Vector3fImmutable dest, float lerpFactor) { + return dest.subtract(this).mult(lerpFactor).add(this).y; + } +} diff --git a/src/engine/mobileAI/MobAI.java b/src/engine/mobileAI/MobAI.java new file mode 100644 index 00000000..b4543321 --- /dev/null +++ b/src/engine/mobileAI/MobAI.java @@ -0,0 +1,1371 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.mobileAI; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.InterestManagement.WorldGrid; +import engine.gameManager.*; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.mobileAI.Threads.MobAIThread; +import engine.mobileAI.utilities.CombatUtilities; +import engine.mobileAI.utilities.MovementUtilities; +import engine.net.DispatchMessage; +import engine.net.client.msg.PerformActionMsg; +import engine.net.client.msg.PowerProjectileMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; + +public class MobAI { + + + private static void AttackTarget(Mob mob, AbstractWorldObject target) { + + try { + + if (mob == null) + return; + + if (target == null || !target.isAlive()) { + mob.setCombatTarget(null); + return; + } + + if (target.getObjectType() == Enum.GameObjectType.PlayerCharacter && canCast(mob)) { + + if (mob.isPlayerGuard() == false && MobCast(mob)) { + mob.updateLocation(); + return; + } + + if (mob.isPlayerGuard() == true && GuardCast(mob)) { + mob.updateLocation(); + return; + } + } + + if (!CombatUtilities.inRangeToAttack(mob, target)) + return; + + switch (target.getObjectType()) { + case PlayerCharacter: + PlayerCharacter targetPlayer = (PlayerCharacter) target; + AttackPlayer(mob, targetPlayer); + break; + case Building: + Building targetBuilding = (Building) target; + AttackBuilding(mob, targetBuilding); + break; + case Mob: + Mob targetMob = (Mob) target; + AttackMob(mob, targetMob); + break; + } + + mob.updateLocation(); + + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage()); + } + } + + public static void AttackPlayer(Mob mob, PlayerCharacter target) { + + try { + + if (!mob.canSee(target)) { + mob.setCombatTarget(null); + return; + } + + if (mob.BehaviourType.callsForHelp) + MobCallForHelp(mob); + + if (!MovementUtilities.inRangeDropAggro(mob, target)) { + mob.setCombatTarget(null); + return; + } + + if (CombatUtilities.inRange2D(mob, target, mob.getRange())) { + + //no weapons, default mob attack speed 3 seconds. + + if (System.currentTimeMillis() < mob.getLastAttackTime()) + return; + + // ranged mobs cant attack while running. skip until they finally stop. + + if (mob.isMoving() && mob.getRange() > 20) + return; + + // add timer for last attack. + + ItemBase mainHand = mob.getWeaponItemBase(true); + ItemBase offHand = mob.getWeaponItemBase(false); + + if (mainHand == null && offHand == null) { + CombatUtilities.combatCycle(mob, target, true, null); + int delay = 3000; + if (mob.isSiege()) + delay = 11000; + mob.setLastAttackTime(System.currentTimeMillis() + delay); + } else if (mob.getWeaponItemBase(true) != null) { + int delay = 3000; + if (mob.isSiege()) + delay = 11000; + CombatUtilities.combatCycle(mob, target, true, mob.getWeaponItemBase(true)); + mob.setLastAttackTime(System.currentTimeMillis() + delay); + } else if (mob.getWeaponItemBase(false) != null) { + int attackDelay = 3000; + if (mob.isSiege()) + attackDelay = 11000; + CombatUtilities.combatCycle(mob, target, false, mob.getWeaponItemBase(false)); + mob.setLastAttackTime(System.currentTimeMillis() + attackDelay); + } + } + + if (target.getPet() != null) + if (target.getPet().getCombatTarget() == null && target.getPet().assist == true) + target.getPet().setCombatTarget(mob); + + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackPlayer" + " " + e.getMessage()); + } + + } + + public static void AttackBuilding(Mob mob, Building target) { + + try { + + if (target.getRank() == -1 || !target.isVulnerable() || BuildingManager.getBuildingFromCache(target.getObjectUUID()) == null) { + mob.setCombatTarget(null); + return; + } + + City playercity = ZoneManager.getCityAtLocation(mob.getLoc()); + + if (playercity != null) + for (Mob guard : playercity.getParent().zoneMobSet) + if (guard.BehaviourType != null && guard.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardCaptain.ordinal()) + if (guard.getCombatTarget() == null && !guard.getGuild().equals(mob.getGuild())) + guard.setCombatTarget(mob); + + if (mob.isSiege()) + MovementManager.sendRWSSMsg(mob); + + ItemBase mainHand = mob.getWeaponItemBase(true); + ItemBase offHand = mob.getWeaponItemBase(false); + + if (mainHand == null && offHand == null) { + CombatUtilities.combatCycle(mob, target, true, null); + int delay = 3000; + if (mob.isSiege()) + delay = 15000; + mob.setLastAttackTime(System.currentTimeMillis() + delay); + } else if (mob.getWeaponItemBase(true) != null) { + int attackDelay = 3000; + if (mob.isSiege()) + attackDelay = 15000; + CombatUtilities.combatCycle(mob, target, true, mob.getWeaponItemBase(true)); + mob.setLastAttackTime(System.currentTimeMillis() + attackDelay); + } else if (mob.getWeaponItemBase(false) != null) { + int attackDelay = 3000; + if (mob.isSiege()) + attackDelay = 15000; + CombatUtilities.combatCycle(mob, target, false, mob.getWeaponItemBase(false)); + mob.setLastAttackTime(System.currentTimeMillis() + attackDelay); + } + + if (mob.isSiege()) { + PowerProjectileMsg ppm = new PowerProjectileMsg(mob, target); + ppm.setRange(50); + DispatchMessage.dispatchMsgToInterestArea(mob, ppm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + } + + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackBuilding" + " " + e.getMessage()); + } + } + + public static void AttackMob(Mob mob, Mob target) { + + try { + + if (mob.getRange() >= 30 && mob.isMoving()) + return; + + //no weapons, default mob attack speed 3 seconds. + + ItemBase mainHand = mob.getWeaponItemBase(true); + ItemBase offHand = mob.getWeaponItemBase(false); + + if (mainHand == null && offHand == null) { + CombatUtilities.combatCycle(mob, target, true, null); + int delay = 3000; + if (mob.isSiege()) + delay = 11000; + mob.setLastAttackTime(System.currentTimeMillis() + delay); + } else if (mob.getWeaponItemBase(true) != null) { + int attackDelay = 3000; + if (mob.isSiege()) + attackDelay = 11000; + CombatUtilities.combatCycle(mob, target, true, mob.getWeaponItemBase(true)); + mob.setLastAttackTime(System.currentTimeMillis() + attackDelay); + } else if (mob.getWeaponItemBase(false) != null) { + int attackDelay = 3000; + if (mob.isSiege()) + attackDelay = 11000; + CombatUtilities.combatCycle(mob, target, false, mob.getWeaponItemBase(false)); + mob.setLastAttackTime(System.currentTimeMillis() + attackDelay); + if (target.getCombatTarget() == null) { + target.setCombatTarget(mob); + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackMob" + " " + e.getMessage()); + } + } + + private static void Patrol(Mob mob) { + + try { + + //make sure mob is out of combat stance + + int patrolDelay = ThreadLocalRandom.current().nextInt((int) (MobAIThread.AI_PATROL_DIVISOR * 0.5f), MobAIThread.AI_PATROL_DIVISOR) + MobAIThread.AI_PATROL_DIVISOR; + + //early exit while waiting to patrol again + + if (mob.stopPatrolTime + (patrolDelay * 1000) > System.currentTimeMillis()) + return; + + //guard captains inherit barracks patrol points dynamically + + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardCaptain.ordinal()) { + + Building barracks = mob.building; + + if (barracks != null && barracks.patrolPoints != null && !barracks.getPatrolPoints().isEmpty()) { + mob.patrolPoints = barracks.patrolPoints; + } else { + randomGuardPatrolPoint(mob); + return; + } + } + + if (mob.lastPatrolPointIndex > mob.patrolPoints.size() - 1) + mob.lastPatrolPointIndex = 0; + + mob.destination = mob.patrolPoints.get(mob.lastPatrolPointIndex); + mob.lastPatrolPointIndex += 1; + + MovementUtilities.aiMove(mob, mob.destination, true); + + if (mob.BehaviourType.equals(Enum.MobBehaviourType.GuardCaptain)) + for (Entry minion : mob.siegeMinionMap.entrySet()) + + //make sure mob is out of combat stance + + if (minion.getKey().despawned == false) { + if (MovementUtilities.canMove(minion.getKey())) { + Vector3f minionOffset = Formation.getOffset(2, minion.getValue() + 3); + minion.getKey().updateLocation(); + Vector3fImmutable formationPatrolPoint = new Vector3fImmutable(mob.destination.x + minionOffset.x, mob.destination.y, mob.destination.z + minionOffset.z); + MovementUtilities.aiMove(minion.getKey(), formationPatrolPoint, true); + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage()); + } + } + + public static boolean canCast(Mob mob) { + + try { + + // Performs validation to determine if a + // mobile in the proper state to cast. + + if (mob == null) + return false; + + if(mob.isPlayerGuard == true){ + + int contractID; + + if(mob.BehaviourType.equals(Enum.MobBehaviourType.GuardMinion)) + contractID = mob.npcOwner.contract.getContractID(); + else + contractID = mob.contract.getContractID(); + + if(Enum.MinionType.ContractToMinionMap.get(contractID).isMage() == false) + return false; + } + + if (mob.mobPowers.isEmpty()) + return false; + + if (!mob.canSee((PlayerCharacter) mob.getCombatTarget())) { + mob.setCombatTarget(null); + return false; + } + if (mob.nextCastTime == 0) + mob.nextCastTime = System.currentTimeMillis(); + + return mob.nextCastTime <= System.currentTimeMillis(); + + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: canCast" + " " + e.getMessage()); + } + return false; + } + + public static boolean MobCast(Mob mob) { + + try { + // Method picks a random spell from a mobile's list of powers + // and casts it on the current target (or itself). Validation + // (including empty lists) is done previously within canCast(); + + ArrayList powerTokens; + ArrayList purgeTokens; + AbstractCharacter target = (AbstractCharacter) mob.getCombatTarget(); + + if (mob.BehaviourType.callsForHelp) + MobCallForHelp(mob); + + // Generate a list of tokens from the mob powers for this mobile. + + powerTokens = new ArrayList<>(mob.mobPowers.keySet()); + purgeTokens = new ArrayList<>(); + + // If player has this effect on them currently then remove + // this token from our list. + + for (int powerToken : powerTokens) { + + PowersBase powerBase = PowersManager.getPowerByToken(powerToken); + + for (ActionsBase actionBase : powerBase.getActions()) { + + String stackType = actionBase.stackType; + + if (target.getEffects() != null && target.getEffects().containsKey(stackType)) + purgeTokens.add(powerToken); + } + } + + powerTokens.removeAll(purgeTokens); + + // Sanity check + + if (powerTokens.isEmpty()) + return false; + + // Pick random spell from our list of powers + + int powerToken = powerTokens.get(ThreadLocalRandom.current().nextInt(powerTokens.size())); + int powerRank = mob.mobPowers.get(powerToken); + + PowersBase mobPower = PowersManager.getPowerByToken(powerToken); + + //check for hit-roll + + if (mobPower.requiresHitRoll) + if (CombatUtilities.triggerDefense(mob, mob.getCombatTarget())) + return false; + + // Cast the spell + + if (CombatUtilities.inRange2D(mob, mob.getCombatTarget(), mobPower.getRange())) { + + PerformActionMsg msg; + + if (!mobPower.isHarmful() || mobPower.targetSelf) { + PowersManager.useMobPower(mob, mob, mobPower, powerRank); + msg = PowersManager.createPowerMsg(mobPower, powerRank, mob, mob); + } else { + PowersManager.useMobPower(mob, target, mobPower, powerRank); + msg = PowersManager.createPowerMsg(mobPower, powerRank, mob, target); + } + + msg.setUnknown04(2); + + PowersManager.finishUseMobPower(msg, mob, 0, 0); + long randomCooldown = (long)((ThreadLocalRandom.current().nextInt(10,15) * 1000) * MobAIThread.AI_CAST_FREQUENCY); + + mob.nextCastTime = System.currentTimeMillis() + randomCooldown; + return true; + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast" + " " + e.getMessage()); + } + return false; + } + + public static boolean GuardCast(Mob mob) { + + try { + // Method picks a random spell from a mobile's list of powers + // and casts it on the current target (or itself). Validation + // (including empty lists) is done previously within canCast(); + + ArrayList powerTokens; + ArrayList purgeTokens; + AbstractCharacter target = (AbstractCharacter) mob.getCombatTarget(); + + if (mob.BehaviourType.callsForHelp) + MobCallForHelp(mob); + + // Generate a list of tokens from the mob powers for this mobile. + + powerTokens = new ArrayList<>(mob.mobPowers.keySet()); + purgeTokens = new ArrayList<>(); + + // If player has this effect on them currently then remove + // this token from our list. + + for (int powerToken : powerTokens) { + + PowersBase powerBase = PowersManager.getPowerByToken(powerToken); + + for (ActionsBase actionBase : powerBase.getActions()) { + + String stackType = actionBase.stackType; + + if (target.getEffects() != null && target.getEffects().containsKey(stackType)) + purgeTokens.add(powerToken); + } + } + + powerTokens.removeAll(purgeTokens); + + // Sanity check + + if (powerTokens.isEmpty()) + return false; + + int powerToken = 0; + int nukeRoll = ThreadLocalRandom.current().nextInt(1,100); + + if (nukeRoll < 55) { + + //use direct damage spell + powerToken = powerTokens.get(powerTokens.size() - 1); + + } else { + //use random spell + powerToken = powerTokens.get(ThreadLocalRandom.current().nextInt(powerTokens.size())); + } + + int powerRank = 1; + + switch(mob.getRank()){ + case 1: + powerRank = 10; + break; + case 2: + powerRank = 15; + break; + case 3: + powerRank = 20; + break; + case 4: + powerRank = 25; + break; + case 5: + powerRank = 30; + break; + case 6: + powerRank = 35; + break; + case 7: + powerRank = 40; + break; + } + + PowersBase mobPower = PowersManager.getPowerByToken(powerToken); + + //check for hit-roll + + if (mobPower.requiresHitRoll) + if (CombatUtilities.triggerDefense(mob, mob.getCombatTarget())) + return false; + + // Cast the spell + + if (CombatUtilities.inRange2D(mob, mob.getCombatTarget(), mobPower.getRange())) { + + PerformActionMsg msg; + + if (!mobPower.isHarmful() || mobPower.targetSelf) { + + if (mobPower.category.equals("DISPEL")) { + PowersManager.useMobPower(mob, target, mobPower, powerRank); + msg = PowersManager.createPowerMsg(mobPower, powerRank, mob, target); + } else { + PowersManager.useMobPower(mob, mob, mobPower, powerRank); + msg = PowersManager.createPowerMsg(mobPower, powerRank, mob, mob); + } + } else { + PowersManager.useMobPower(mob, target, mobPower, powerRank); + msg = PowersManager.createPowerMsg(mobPower, powerRank, mob, target); + } + + msg.setUnknown04(2); + + PowersManager.finishUseMobPower(msg, mob, 0, 0); + + long randomCooldown = (long)((ThreadLocalRandom.current().nextInt(10,15) * 1000) * MobAIThread.AI_CAST_FREQUENCY); + mob.nextCastTime = System.currentTimeMillis() + randomCooldown; + return true; + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast" + " " + e.getMessage()); + } + return false; + } + + public static void MobCallForHelp(Mob mob) { + + try { + + boolean callGotResponse = false; + + if (mob.nextCallForHelp == 0) + mob.nextCallForHelp = System.currentTimeMillis(); + + if (mob.nextCallForHelp < System.currentTimeMillis()) + return; + + //mob sends call for help message + + ChatManager.chatSayInfo(null, mob.getName() + " calls for help!"); + + Zone mobCamp = mob.getParentZone(); + + for (Mob helper : mobCamp.zoneMobSet) { + if (helper.BehaviourType.respondsToCallForHelp && helper.BehaviourType.BehaviourHelperType.equals(mob.BehaviourType)) { + helper.setCombatTarget(mob.getCombatTarget()); + callGotResponse = true; + } + } + + //wait 60 seconds to call for help again + + if (callGotResponse) + mob.nextCallForHelp = System.currentTimeMillis() + 60000; + + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCallForHelp" + " " + e.getMessage()); + } + } + + public static void DetermineAction(Mob mob) { + + try { + + //always check the respawn que, respawn 1 mob max per second to not flood the client + + if (mob == null) + return; + + if (mob.getTimestamps().containsKey("lastExecution") == false) + mob.getTimestamps().put("lastExecution", System.currentTimeMillis()); + + if (System.currentTimeMillis() < mob.getTimeStamp("lastExecution")) + return; + + mob.getTimestamps().put("lastExecution", System.currentTimeMillis() + MobAIThread.AI_PULSE_MOB_THRESHOLD); + + //trebuchet spawn handler + + if (mob.despawned && mob.getMobBase().getLoadID() == 13171) { + CheckForRespawn(mob); + return; + } + + //override for guards + + if (mob.despawned && mob.isPlayerGuard) { + + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardMinion.ordinal()) { + if (mob.npcOwner.isAlive() == false || ((Mob) mob.npcOwner).despawned == true) { + + //minions don't respawn while guard captain is dead + + if (mob.isAlive() == false) { + mob.deathTime = System.currentTimeMillis(); + return; + } + + } + } + + CheckForRespawn(mob); + + //check to send mob home for player guards to prevent exploit of dragging guards away and then teleporting + + if (mob.BehaviourType.ordinal() != Enum.MobBehaviourType.Pet1.ordinal()) + CheckToSendMobHome(mob); + + return; + } + + //no need to continue if mob is dead, check for respawn and move on + + if (!mob.isAlive()) { + CheckForRespawn(mob); + return; + } + + //no players loaded, no need to proceed + + if (mob.playerAgroMap.isEmpty()) { + if(mob.getCombatTarget() != null) + mob.setCombatTarget(null); + return; + } + + if (mob.BehaviourType.ordinal() != Enum.MobBehaviourType.Pet1.ordinal()) + CheckToSendMobHome(mob); + + if (mob.getCombatTarget() != null) { + + if (mob.getCombatTarget().isAlive() == false) { + mob.setCombatTarget(null); + return; + } + + if (mob.getCombatTarget().getObjectTypeMask() == MBServerStatics.MASK_PLAYER) { + + PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget(); + + if (mob.playerAgroMap.containsKey(target.getObjectUUID()) == false) { + mob.setCombatTarget(null); + return; + } + + if (mob.canSee((PlayerCharacter) mob.getCombatTarget()) == false) { + mob.setCombatTarget(null); + return; + } + + } + } + + switch (mob.BehaviourType) { + case GuardCaptain: + GuardCaptainLogic(mob); + break; + case GuardMinion: + GuardMinionLogic(mob); + break; + case GuardWallArcher: + GuardWallArcherLogic(mob); + break; + case Pet1: + PetLogic(mob); + break; + case HamletGuard: + HamletGuardLogic(mob); + break; + default: + DefaultLogic(mob); + break; + } + if(mob.isAlive()) + RecoverHealth(mob); + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DetermineAction" + " " + e.getMessage()); + } + } + + private static void CheckForAggro(Mob aiAgent) { + + try { + + //looks for and sets mobs combatTarget + + if (!aiAgent.isAlive()) + return; + + ConcurrentHashMap loadedPlayers = aiAgent.playerAgroMap; + + for (Entry playerEntry : loadedPlayers.entrySet()) { + + int playerID = (int) playerEntry.getKey(); + PlayerCharacter loadedPlayer = PlayerCharacter.getFromCache(playerID); + + //Player is null, let's remove them from the list. + + if (loadedPlayer == null) { + loadedPlayers.remove(playerID); + continue; + } + + //Player is Dead, Mob no longer needs to attempt to aggro. Remove them from aggro map. + + if (!loadedPlayer.isAlive()) { + loadedPlayers.remove(playerID); + continue; + } + + //Can't see target, skip aggro. + + if (!aiAgent.canSee(loadedPlayer)) + continue; + + // No aggro for this race type + + if (aiAgent.notEnemy.size() > 0 && aiAgent.notEnemy.contains(loadedPlayer.getRace().getRaceType().getMonsterType()) == true) + continue; + + //mob has enemies and this player race is not it + + if (aiAgent.enemy.size() > 0 && aiAgent.enemy.contains(loadedPlayer.getRace().getRaceType().getMonsterType()) == false) + continue; + + if (MovementUtilities.inRangeToAggro(aiAgent, loadedPlayer)) { + aiAgent.setCombatTarget(loadedPlayer); + return; + } + + } + + if (aiAgent.getCombatTarget() == null) { + + //look for pets to aggro if no players found to aggro + + HashSet awoList = WorldGrid.getObjectsInRangePartial(aiAgent, MobAIThread.AI_BASE_AGGRO_RANGE, MBServerStatics.MASK_PET); + + for (AbstractWorldObject awoMob : awoList) { + + // exclude self. + + if (aiAgent.equals(awoMob)) + continue; + + Mob aggroMob = (Mob) awoMob; + aiAgent.setCombatTarget(aggroMob); + return; + } + } + } catch (Exception e) { + Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForAggro" + " " + e.getMessage()); + } + } + + private static void CheckMobMovement(Mob mob) { + + try { + + if (!MovementUtilities.canMove(mob)) + return; + + mob.updateLocation(); + + switch (mob.BehaviourType) { + + case Pet1: + if (mob.getOwner() == null) + return; + + if (!mob.playerAgroMap.containsKey(mob.getOwner().getObjectUUID())) { + + //mob no longer has its owner loaded, translocate pet to owner + + MovementManager.translocate(mob, mob.getOwner().getLoc(), null); + return; + } + if (mob.getCombatTarget() == null) { + + //move back to owner + + if (CombatUtilities.inRange2D(mob, mob.getOwner(), 6)) + return; + + mob.destination = mob.getOwner().getLoc(); + MovementUtilities.moveToLocation(mob, mob.destination, 5); + } else + chaseTarget(mob); + break; + case GuardMinion: + if (!mob.npcOwner.isAlive() && mob.getCombatTarget() == null) + randomGuardPatrolPoint(mob); + else { + if (mob.getCombatTarget() != null) { + chaseTarget(mob); + } + } + break; + default: + if (mob.getCombatTarget() == null) { + if (!mob.isMoving()) + Patrol(mob); + else { + mob.stopPatrolTime = System.currentTimeMillis(); + } + } else { + chaseTarget(mob); + } + break; + + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckMobMovement" + " " + e.getMessage()); + } + } + + private static void CheckForRespawn(Mob aiAgent) { + + try { + + if (aiAgent.deathTime == 0) { + aiAgent.setDeathTime(System.currentTimeMillis()); + return; + } + + //handles checking for respawn of dead mobs even when no players have mob loaded + //Despawn Timer with Loot currently in inventory. + + if (!aiAgent.despawned) { + + if (aiAgent.getCharItemManager().getInventoryCount() > 0) { + if (System.currentTimeMillis() > aiAgent.deathTime + MBServerStatics.DESPAWN_TIMER_WITH_LOOT) { + aiAgent.despawn(); + aiAgent.deathTime = System.currentTimeMillis(); + return; + } + //No items in inventory. + } else { + //Mob's Loot has been looted. + if (aiAgent.isHasLoot()) { + if (System.currentTimeMillis() > aiAgent.deathTime + MBServerStatics.DESPAWN_TIMER_ONCE_LOOTED) { + aiAgent.despawn(); + aiAgent.deathTime = System.currentTimeMillis(); + return; + } + //Mob never had Loot. + } else { + if (System.currentTimeMillis() > aiAgent.deathTime + MBServerStatics.DESPAWN_TIMER) { + aiAgent.despawn(); + aiAgent.deathTime = System.currentTimeMillis(); + return; + } + } + } + } else if (System.currentTimeMillis() > (aiAgent.deathTime + (aiAgent.spawnTime * 1000))) { + + if (Zone.respawnQue.contains(aiAgent) == false) { + Zone.respawnQue.add(aiAgent); + } + } + } catch (Exception e) { + Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn" + " " + e.getMessage()); + } + } + + public static void CheckForAttack(Mob mob) { + try { + + //checks if mob can attack based on attack timer and range + + if (mob.isAlive() == false) + return; + + if (mob.getCombatTarget() == null) + return; + + if (mob.getCombatTarget().getObjectType().equals(Enum.GameObjectType.PlayerCharacter) && MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) == false && mob.BehaviourType.ordinal() != Enum.MobBehaviourType.Pet1.ordinal()) { + + mob.setCombatTarget(null); + return; + } + if (System.currentTimeMillis() > mob.getLastAttackTime()) + AttackTarget(mob, mob.getCombatTarget()); + + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForAttack" + " " + e.getMessage()); + } + } + + private static void CheckToSendMobHome(Mob mob) { + + try { + if (mob.BehaviourType.isAgressive) { + + if (mob.isPlayerGuard()) { + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardCaptain.ordinal()) + CheckForPlayerGuardAggro(mob); + } else { + CheckForAggro(mob); + } + } + + if (mob.getCombatTarget() != null && CombatUtilities.inRange2D(mob, mob.getCombatTarget(), MobAIThread.AI_BASE_AGGRO_RANGE * 0.5f)) + return; + + if (mob.isPlayerGuard() && !mob.despawned) { + + City current = ZoneManager.getCityAtLocation(mob.getLoc()); + + if (current == null || current.equals(mob.getGuild().getOwnedCity()) == false) { + + PowersBase recall = PowersManager.getPowerByToken(-1994153779); + PowersManager.useMobPower(mob, mob, recall, 40); + mob.setCombatTarget(null); + + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardCaptain.ordinal() && mob.isAlive()) { + + //guard captain pulls his minions home with him + + for (Entry minion : mob.siegeMinionMap.entrySet()) { + PowersManager.useMobPower(minion.getKey(), minion.getKey(), recall, 40); + minion.getKey().setCombatTarget(null); + } + } + } + } else if (MovementUtilities.inRangeOfBindLocation(mob) == false) { + + PowersBase recall = PowersManager.getPowerByToken(-1994153779); + PowersManager.useMobPower(mob, mob, recall, 40); + mob.setCombatTarget(null); + + for (Entry playerEntry : mob.playerAgroMap.entrySet()) + PlayerCharacter.getFromCache((int) playerEntry.getKey()).setHateValue(0); + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckToSendMobHome" + " " + e.getMessage()); + } + } + + private static void chaseTarget(Mob mob) { + + try { + + float rangeSquared = mob.getRange() * mob.getRange(); + float distanceSquared = mob.getLoc().distanceSquared2D(mob.getCombatTarget().getLoc()); + + if(mob.isMoving() == true && distanceSquared < rangeSquared - 50) { + mob.destination = mob.getLoc(); + MovementUtilities.moveToLocation(mob, mob.destination, 0); + } else if (CombatUtilities.inRange2D(mob, mob.getCombatTarget(), mob.getRange()) == false) { + if (mob.getRange() > 15) { + mob.destination = mob.getCombatTarget().getLoc(); + MovementUtilities.moveToLocation(mob, mob.destination, 0); + } else { + + //check if building + + switch (mob.getCombatTarget().getObjectType()) { + case PlayerCharacter: + case Mob: + mob.destination = MovementUtilities.GetDestinationToCharacter(mob, (AbstractCharacter) mob.getCombatTarget()); + MovementUtilities.moveToLocation(mob, mob.destination, mob.getRange() + 1); + break; + case Building: + mob.destination = mob.getCombatTarget().getLoc(); + MovementUtilities.moveToLocation(mob, mob.getCombatTarget().getLoc(), 0); + break; + } + } + } + mob.updateMovementState(); + mob.updateLocation(); + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: chaseTarget" + " " + e.getMessage()); + } + } + + private static void SafeGuardAggro(Mob mob) { + try { + + HashSet awoList = WorldGrid.getObjectsInRangePartial(mob, 100, MBServerStatics.MASK_MOB); + + for (AbstractWorldObject awoMob : awoList) { + + //dont scan self. + + if (mob.equals(awoMob) || (mob.agentType.equals(Enum.AIAgentType.GUARD)) == true) + continue; + + Mob aggroMob = (Mob) awoMob; + + //don't attack other guards + + if ((aggroMob.agentType.equals(Enum.AIAgentType.GUARD))) + continue; + + if(aggroMob.BehaviourType.equals(Enum.MobBehaviourType.Pet1)) + continue; + + if (mob.getLoc().distanceSquared2D(aggroMob.getLoc()) > sqr(50)) + continue; + + mob.setCombatTarget(aggroMob); + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: SafeGuardAggro" + " " + e.getMessage()); + } + } + + public static void GuardCaptainLogic(Mob mob) { + + try { + if (mob.getCombatTarget() == null) + CheckForPlayerGuardAggro(mob); + + AbstractWorldObject newTarget = ChangeTargetFromHateValue(mob); + + if (newTarget != null) { + + if (newTarget.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + if (GuardCanAggro(mob, (PlayerCharacter) newTarget)) + mob.setCombatTarget(newTarget); + } else + mob.setCombatTarget(newTarget); + + } + CheckMobMovement(mob); + CheckForAttack(mob); + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCaptainLogic" + " " + e.getMessage()); + } + } + + public static void GuardMinionLogic(Mob mob) { + + try { + boolean isComanded = mob.npcOwner.isAlive(); + if (!isComanded) { + GuardCaptainLogic(mob); + }else { + if (mob.npcOwner.getCombatTarget() != null) + mob.setCombatTarget(mob.npcOwner.getCombatTarget()); + else + if (mob.getCombatTarget() != null) + mob.setCombatTarget(null); + } + CheckMobMovement(mob); + CheckForAttack(mob); + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardMinionLogic" + " " + e.getMessage()); + } + } + + public static void GuardWallArcherLogic(Mob mob) { + + try { + if (mob.getCombatTarget() == null) + CheckForPlayerGuardAggro(mob); + else + CheckForAttack(mob); + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardWallArcherLogic" + " " + e.getMessage()); + } + } + + private static void PetLogic(Mob mob) { + + try { + + if (mob.getOwner() == null && mob.isNecroPet() == false && mob.isSiege() == false) + if (ZoneManager.getSeaFloor().zoneMobSet.contains(mob)) + mob.killCharacter("no owner"); + + if (MovementUtilities.canMove(mob) && mob.BehaviourType.canRoam) + CheckMobMovement(mob); + + CheckForAttack(mob); + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: PetLogic" + " " + e.getMessage()); + } + } + + private static void HamletGuardLogic(Mob mob) { + + try { + //safehold guard + + if (mob.getCombatTarget() == null) + SafeGuardAggro(mob); + else if (mob.getCombatTarget().isAlive() == false) + SafeGuardAggro(mob); + + CheckForAttack(mob); + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: HamletGuardLogic" + " " + e.getMessage()); + } + } + + private static void DefaultLogic(Mob mob) { + + try { + + //check for players that can be aggroed if mob is agressive and has no target + + if (mob.getCombatTarget() != null && mob.playerAgroMap.containsKey(mob.getCombatTarget().getObjectUUID()) == false) + mob.setCombatTarget(null); + + if (mob.BehaviourType.isAgressive) { + + AbstractWorldObject newTarget = ChangeTargetFromHateValue(mob); + + if (newTarget != null) + mob.setCombatTarget(newTarget); + else { + if (mob.getCombatTarget() == null) { + if (mob.BehaviourType == Enum.MobBehaviourType.HamletGuard) + SafeGuardAggro(mob); //safehold guard + else + CheckForAggro(mob); //normal aggro + } + } + } + + //check if mob can move for patrol or moving to target + + if (mob.BehaviourType.canRoam) + CheckMobMovement(mob); + + //check if mob can attack if it isn't wimpy + + if (!mob.BehaviourType.isWimpy && mob.getCombatTarget() != null) + CheckForAttack(mob); + + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DefaultLogic" + " " + e.getMessage()); + } + } + + public static void CheckForPlayerGuardAggro(Mob mob) { + + try { + + //looks for and sets mobs combatTarget + + if (!mob.isAlive()) + return; + + ConcurrentHashMap loadedPlayers = mob.playerAgroMap; + + for (Entry playerEntry : loadedPlayers.entrySet()) { + + int playerID = (int) playerEntry.getKey(); + PlayerCharacter loadedPlayer = PlayerCharacter.getFromCache(playerID); + + //Player is null, let's remove them from the list. + + if (loadedPlayer == null) { + loadedPlayers.remove(playerID); + continue; + } + + //Player is Dead, Mob no longer needs to attempt to aggro. Remove them from aggro map. + + if (!loadedPlayer.isAlive()) { + loadedPlayers.remove(playerID); + continue; + } + + //Can't see target, skip aggro. + + if (!mob.canSee(loadedPlayer)) + continue; + + // No aggro for this player + + if (GuardCanAggro(mob, loadedPlayer) == false) + continue; + + if (MovementUtilities.inRangeToAggro(mob, loadedPlayer) && mob.getCombatTarget() == null) { + mob.setCombatTarget(loadedPlayer); + return; + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro" + e.getMessage()); + } + } + + public static Boolean GuardCanAggro(Mob mob, PlayerCharacter target) { + + try { + + if (mob.getGuild().getNation().equals(target.getGuild().getNation())) + return false; + + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardMinion.ordinal()) { + if (((Mob) mob.npcOwner).building.getCity().cityOutlaws.contains(target.getObjectUUID()) == true) { + return true; + } + } else if (mob.building.getCity().cityOutlaws.contains(target.getObjectUUID()) == true) { + return true; + } + + //first check condemn list for aggro allowed (allies button is checked) + + if (ZoneManager.getCityAtLocation(mob.getLoc()).getTOL().reverseKOS) { + for (Entry entry : ZoneManager.getCityAtLocation(mob.getLoc()).getTOL().getCondemned().entrySet()) { + + //target is listed individually + + if (entry.getValue().getPlayerUID() == target.getObjectUUID() && entry.getValue().isActive()) + return false; + + //target's guild is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild()) + return false; + + //target's nation is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild().getNation()) + return false; + } + return true; + } else { + + //allies button is not checked + + for (Entry entry : ZoneManager.getCityAtLocation(mob.getLoc()).getTOL().getCondemned().entrySet()) { + + //target is listed individually + + if (entry.getValue().getPlayerUID() == target.getObjectUUID() && entry.getValue().isActive()) + return true; + + //target's guild is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild()) + return true; + + //target's nation is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild().getNation()) + return true; + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro" + " " + e.getMessage()); + } + return false; + } + + public static void randomGuardPatrolPoint(Mob mob) { + + try { + + //early exit for a mob who is already moving to a patrol point + //while mob moving, update lastPatrolTime so that when they stop moving the 10 second timer can begin + + if (mob.isMoving() == true) { + mob.stopPatrolTime = System.currentTimeMillis(); + return; + } + + //wait between 10 and 15 seconds after reaching patrol point before moving + + int patrolDelay = ThreadLocalRandom.current().nextInt(10000) + 5000; + + //early exit while waiting to patrol again + + if (mob.stopPatrolTime + patrolDelay > System.currentTimeMillis()) + return; + + float xPoint = ThreadLocalRandom.current().nextInt(400) - 200; + float zPoint = ThreadLocalRandom.current().nextInt(400) - 200; + Vector3fImmutable TreePos = mob.getGuild().getOwnedCity().getLoc(); + mob.destination = new Vector3fImmutable(TreePos.x + xPoint, TreePos.y, TreePos.z + zPoint); + + MovementUtilities.aiMove(mob, mob.destination, true); + + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardCaptain.ordinal()) { + for (Entry minion : mob.siegeMinionMap.entrySet()) { + + //make sure mob is out of combat stance + + if (minion.getKey().despawned == false) { + if (MovementUtilities.canMove(minion.getKey())) { + Vector3f minionOffset = Formation.getOffset(2, minion.getValue() + 3); + minion.getKey().updateLocation(); + Vector3fImmutable formationPatrolPoint = new Vector3fImmutable(mob.destination.x + minionOffset.x, mob.destination.y, mob.destination.z + minionOffset.z); + MovementUtilities.aiMove(minion.getKey(), formationPatrolPoint, true); + } + } + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints" + " " + e.getMessage()); + } + } + + public static AbstractWorldObject ChangeTargetFromHateValue(Mob mob) { + + try { + + float CurrentHateValue = 0; + + if (mob.getCombatTarget() != null && mob.getCombatTarget().getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) + CurrentHateValue = ((PlayerCharacter) mob.getCombatTarget()).getHateValue(); + + AbstractWorldObject mostHatedTarget = null; + + for (Entry playerEntry : mob.playerAgroMap.entrySet()) { + + PlayerCharacter potentialTarget = PlayerCharacter.getFromCache((int) playerEntry.getKey()); + + if (potentialTarget.equals(mob.getCombatTarget())) + continue; + + if (potentialTarget != null && potentialTarget.getHateValue() > CurrentHateValue && MovementUtilities.inRangeToAggro(mob, potentialTarget)) { + CurrentHateValue = potentialTarget.getHateValue(); + mostHatedTarget = potentialTarget; + } + + } + return mostHatedTarget; + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: ChangeTargetFromMostHated" + " " + e.getMessage()); + } + return null; + } + + public static void RecoverHealth(Mob mob){ + //recover health + + if (mob.getTimestamps().containsKey("HEALTHRECOVERED") == false) + mob.getTimestamps().put("HEALTHRECOVERED", System.currentTimeMillis()); + + if (mob.isSit() && mob.getTimeStamp("HEALTHRECOVERED") < System.currentTimeMillis() + 3000) + if (mob.getHealth() < mob.getHealthMax()) { + + float recoveredHealth = mob.getHealthMax() * ((1 + mob.getBonuses().getFloatPercentAll(Enum.ModType.HealthRecoverRate, Enum.SourceType.None)) * 0.01f); + mob.setHealth(mob.getHealth() + recoveredHealth); + mob.getTimestamps().put("HEALTHRECOVERED", System.currentTimeMillis()); + + if (mob.getHealth() > mob.getHealthMax()) + mob.setHealth(mob.getHealthMax()); + } + } +} \ No newline at end of file diff --git a/src/engine/mobileAI/Threads/MobAIThread.java b/src/engine/mobileAI/Threads/MobAIThread.java new file mode 100644 index 00000000..b3699879 --- /dev/null +++ b/src/engine/mobileAI/Threads/MobAIThread.java @@ -0,0 +1,51 @@ +package engine.mobileAI.Threads; + +import engine.gameManager.ConfigManager; +import engine.mobileAI.MobAI; +import engine.gameManager.ZoneManager; +import engine.objects.Mob; +import engine.objects.Zone; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +public class MobAIThread implements Runnable{ + public static int AI_BASE_AGGRO_RANGE = 60; + public static int AI_DROP_AGGRO_RANGE = 60; + public static int AI_PULSE_MOB_THRESHOLD = 200; + public static int AI_PATROL_DIVISOR = 15; + public static float AI_CAST_FREQUENCY; + // Thread constructor + + public MobAIThread() { + Logger.info(" MobAIThread thread has started!"); + + } + + @Override + public void run() { + //cache config value for mobile casting delay + AI_CAST_FREQUENCY = Float.parseFloat(ConfigManager.MB_AI_CAST_FREQUENCY.getValue()); + AI_BASE_AGGRO_RANGE = (int)(60 * Float.parseFloat(ConfigManager.MB_AI_AGGRO_RANGE.getValue())); + while (true) { + for (Zone zone : ZoneManager.getAllZones()) { + + for (Mob mob : zone.zoneMobSet) { + + try { + if (mob != null) + MobAI.DetermineAction(mob); + } catch (Exception e) { + Logger.error("Mob: " + mob.getName() + " UUID: " + mob.getObjectUUID() + " ERROR: " + e); + e.printStackTrace(); + } + } + } + } + } + public static void startAIThread() { + Thread aiThread; + aiThread = new Thread(new MobAIThread()); + aiThread.setName("aiThread"); + aiThread.start(); + } +} diff --git a/src/engine/mobileAI/Threads/MobRespawnThread.java b/src/engine/mobileAI/Threads/MobRespawnThread.java new file mode 100644 index 00000000..24e2f281 --- /dev/null +++ b/src/engine/mobileAI/Threads/MobRespawnThread.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.mobileAI.Threads; +import engine.gameManager.ZoneManager; +import engine.objects.Mob; +import engine.objects.Zone; +import org.pmw.tinylog.Logger; + +/** + * Thread blocks until MagicBane dispatch messages are + * enqueued then processes them in FIFO order. The collection + * is thread safe. + *

+ * Any large messages not time sensitive such as load object + * sent to more than a single individual should be spawned + * individually on a DispatchMessageThread. + */ + +public class MobRespawnThread implements Runnable { + + + public MobRespawnThread() { + Logger.info(" MobRespawnThread thread has started!"); + + } + + @Override + public void run() { + + while (true) { + + try { + for (Zone zone : ZoneManager.getAllZones()) { + + if (zone.respawnQue.isEmpty() == false && zone.lastRespawn + 100 < System.currentTimeMillis()) { + + Mob respawner = zone.respawnQue.iterator().next(); + + if (respawner == null) + continue; + + respawner.respawn(); + zone.respawnQue.remove(respawner); + zone.lastRespawn = System.currentTimeMillis(); + } + } + } catch (Exception e) { + Logger.error(e); + } + + } + } + public static void startRespawnThread() { + Thread respawnThread; + respawnThread = new Thread(new MobRespawnThread()); + respawnThread.setName("respawnThread"); + respawnThread.start(); + } +} diff --git a/src/engine/mobileAI/utilities/CombatUtilities.java b/src/engine/mobileAI/utilities/CombatUtilities.java new file mode 100644 index 00000000..fd52a2e3 --- /dev/null +++ b/src/engine/mobileAI/utilities/CombatUtilities.java @@ -0,0 +1,514 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.mobileAI.utilities; + +import engine.Enum.*; +import engine.gameManager.ChatManager; +import engine.gameManager.CombatManager; +import engine.math.Vector3fImmutable; +import engine.net.DispatchMessage; +import engine.net.client.msg.TargetedActionMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; +import static java.lang.Math.pow; + +public class CombatUtilities { + + public static boolean inRangeToAttack(Mob agent, AbstractWorldObject target) { + + if (Float.isNaN(agent.getLoc().x)) + return false; + + try { + Vector3fImmutable sl = agent.getLoc(); + Vector3fImmutable tl = target.getLoc(); + + //add Hitbox's to range. + float range = agent.getRange(); + range += CombatManager.calcHitBox(target) + CombatManager.calcHitBox(agent); + + return !(sl.distanceSquared(tl) > sqr(range)); + } catch (Exception e) { + Logger.error(e.toString()); + return false; + } + + } + + public static boolean inRange2D(AbstractWorldObject entity1, AbstractWorldObject entity2, double range) { + return entity1.getLoc().distance2D(entity2.getLoc()) < range; + } + + public static void swingIsBlock(Mob agent, AbstractWorldObject target, int animation) { + + if (!target.isAlive()) + return; + + TargetedActionMsg msg = new TargetedActionMsg(agent, animation, target, MBServerStatics.COMBAT_SEND_BLOCK); + + if (target.getObjectType() == GameObjectType.PlayerCharacter) + DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + DispatchMessage.sendToAllInRange(agent, msg); + + } + + public static void swingIsParry(Mob agent, AbstractWorldObject target, int animation) { + + if (!target.isAlive()) + return; + + TargetedActionMsg msg = new TargetedActionMsg(agent, animation, target, MBServerStatics.COMBAT_SEND_PARRY); + + if (target.getObjectType() == GameObjectType.PlayerCharacter) + DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + DispatchMessage.sendToAllInRange(agent, msg); + + } + + public static void swingIsDodge(Mob agent, AbstractWorldObject target, int animation) { + + if (!target.isAlive()) + return; + + TargetedActionMsg msg = new TargetedActionMsg(agent, animation, target, MBServerStatics.COMBAT_SEND_DODGE); + + if (target.getObjectType() == GameObjectType.PlayerCharacter) + DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + DispatchMessage.sendToAllInRange(agent, msg); + } + + public static void swingIsDamage(Mob agent, AbstractWorldObject target, float damage, int animation) { + if (agent.isSiege() == true) { + damage = ThreadLocalRandom.current().nextInt(1000) + 1500; + } + float trueDamage = damage; + + if (!target.isAlive()) + return; + + if (AbstractWorldObject.IsAbstractCharacter(target)) + trueDamage = ((AbstractCharacter) target).modifyHealth(-damage, agent, false); + else if (target.getObjectType() == GameObjectType.Building) + trueDamage = ((Building) target).modifyHealth(-damage, agent); + + //Don't send 0 damage kay thanx. + + if (trueDamage == 0) + return; + + TargetedActionMsg msg = new TargetedActionMsg(agent, target, damage, animation); + + if (target.getObjectType() == GameObjectType.PlayerCharacter) + DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + DispatchMessage.sendToAllInRange(agent, msg); + + //check damage shields + if (AbstractWorldObject.IsAbstractCharacter(target) && target.isAlive() && target.getObjectType() != GameObjectType.Mob) + CombatManager.handleDamageShields(agent, (AbstractCharacter) target, damage); + } + + public static boolean canSwing(Mob agent) { + return (agent.isAlive() && !agent.getBonuses().getBool(ModType.Stunned, SourceType.None)); + } + + public static void swingIsMiss(Mob agent, AbstractWorldObject target, int animation) { + + TargetedActionMsg msg = new TargetedActionMsg(agent, target, 0f, animation); + + if (target.getObjectType() == GameObjectType.PlayerCharacter) + DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + DispatchMessage.sendToAllInRange(agent, msg); + + } + + public static boolean triggerDefense(Mob agent, AbstractWorldObject target) { + int defenseScore = 0; + int attackScore = agent.getAtrHandOne(); + switch (target.getObjectType()) { + case PlayerCharacter: + defenseScore = ((AbstractCharacter) target).getDefenseRating(); + break; + case Mob: + + Mob mob = (Mob) target; + if (mob.isSiege()) + defenseScore = attackScore; + break; + case Building: + return false; + } + + int hitChance; + if (attackScore > defenseScore || defenseScore == 0) + hitChance = 94; + else if (attackScore == defenseScore && target.getObjectType() == GameObjectType.Mob) + hitChance = 10; + else { + float dif = attackScore / defenseScore; + if (dif <= 0.8f) + hitChance = 4; + else + hitChance = ((int) (450 * (dif - 0.8f)) + 4); + if (target.getObjectType() == GameObjectType.Building) + hitChance = 100; + } + return ThreadLocalRandom.current().nextInt(100) > hitChance; + } + + public static boolean triggerBlock(Mob agent, AbstractWorldObject ac) { + return triggerPassive(agent, ac, "Block"); + } + + public static boolean triggerParry(Mob agent, AbstractWorldObject ac) { + return triggerPassive(agent, ac, "Parry"); + } + + public static boolean triggerDodge(Mob agent, AbstractWorldObject ac) { + return triggerPassive(agent, ac, "Dodge"); + } + + public static boolean triggerPassive(Mob agent, AbstractWorldObject ac, String type) { + float chance = 0; + if (AbstractWorldObject.IsAbstractCharacter(ac)) + chance = ((AbstractCharacter) ac).getPassiveChance(type, agent.getLevel(), true); + + if (chance > 75f) + chance = 75f; + if (agent.isSiege() && AbstractWorldObject.IsAbstractCharacter(ac)) + chance = 100; + + return ThreadLocalRandom.current().nextInt(100) < chance; + } + + public static void combatCycle(Mob agent, AbstractWorldObject target, boolean mainHand, ItemBase wb) { + + if (!agent.isAlive() || !target.isAlive()) + return; + + if (target.getObjectType() == GameObjectType.PlayerCharacter) + if (!((PlayerCharacter) target).isActive()) + return; + + int anim = 75; + float speed; + + if (mainHand) + speed = agent.getSpeedHandOne(); + else + speed = agent.getSpeedHandTwo(); + + DamageType dt = DamageType.Crush; + + if (agent.isSiege()) + dt = DamageType.Siege; + if (wb != null) { + anim = CombatManager.getSwingAnimation(wb, null, mainHand); + dt = wb.getDamageType(); + } else if (!mainHand) + return; + + Resists res = null; + PlayerBonuses bonus = null; + + switch (target.getObjectType()) { + case Building: + res = ((Building) target).getResists(); + break; + case PlayerCharacter: + res = ((PlayerCharacter) target).getResists(); + bonus = ((PlayerCharacter) target).getBonuses(); + break; + case Mob: + Mob mob = (Mob) target; + res = mob.getResists(); + bonus = ((Mob) target).getBonuses(); + break; + } + + //must not be immune to all or immune to attack + + if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.ImmuneToAttack)) + if (res != null && (res.immuneToAll() || res.immuneToAttacks() || res.immuneTo(dt))) + return; + + int passiveAnim = CombatManager.getSwingAnimation(wb, null, mainHand); + if (canSwing(agent)) { + if (triggerDefense(agent, target)) { + swingIsMiss(agent, target, passiveAnim); + return; + } else if (triggerDodge(agent, target)) { + swingIsDodge(agent, target, passiveAnim); + return; + } else if (triggerParry(agent, target)) { + swingIsParry(agent, target, passiveAnim); + + return; + } else if (triggerBlock(agent, target)) { + swingIsBlock(agent, target, passiveAnim); + return; + } + if (agent.getEquip().get(1) != null && agent.getEquip().get(2) != null && agent.getEquip().get(2).getItemBase().isShield() == false) { + //mob is duel wielding and should conduct an attack for each hand + ItemBase weapon1 = agent.getEquip().get(1).getItemBase(); + double range1 = getMaxDmg(weapon1.getMinDamage(), agent, weapon1) - getMinDmg(weapon1.getMinDamage(), agent, weapon1); + double damage1 = getMinDmg(weapon1.getMinDamage(), agent, weapon1) + ((ThreadLocalRandom.current().nextFloat() * range1) + (ThreadLocalRandom.current().nextFloat() * range1)) / 2; + swingIsDamage(agent, target, (float) damage1, CombatManager.getSwingAnimation(weapon1, null, true)); + ItemBase weapon2 = agent.getEquip().get(2).getItemBase(); + double range2 = getMaxDmg(weapon2.getMinDamage(), agent, weapon2) - getMinDmg(weapon2.getMinDamage(), agent, weapon2); + double damage2 = getMinDmg(weapon2.getMinDamage(), agent, weapon2) + ((ThreadLocalRandom.current().nextFloat() * range2) + (ThreadLocalRandom.current().nextFloat() * range2)) / 2; + swingIsDamage(agent, target, (float) damage2, CombatManager.getSwingAnimation(weapon1, null, false)); + } else { + swingIsDamage(agent, target, determineDamage(agent), anim); + } + + if (agent.getWeaponPower() != null) + agent.getWeaponPower().attack(target, MBServerStatics.ONE_MINUTE); + } + + if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) { + PlayerCharacter player = (PlayerCharacter) target; + + if (player.getDebug(64)) + ChatManager.chatSayInfo(player, "Debug Combat: Mob UUID " + agent.getObjectUUID() + " || Building ID = " + agent.getBuildingID() + " || Floor = " + agent.getInFloorID() + " || Level = " + agent.getInBuilding());//combat debug + } + + //SIEGE MONSTERS DO NOT ATTACK GUARDSs + if (target.getObjectType() == GameObjectType.Mob) + if (((Mob) target).isSiege()) + return; + + //handle the retaliate + + if (AbstractWorldObject.IsAbstractCharacter(target)) + CombatManager.handleRetaliate((AbstractCharacter) target, agent); + + if (target.getObjectType() == GameObjectType.Mob) { + Mob targetMob = (Mob) target; + if (targetMob.isSiege()) + return; + } + + } + + public static float determineDamage(Mob agent) { + + //early exit for null + + if (agent == null) + return 0; + + AbstractWorldObject target = agent.getCombatTarget(); + + if (target == null) + return 0; + + float damage = 0; + + DamageType dt = getDamageType(agent); + if ((agent.agentType.equals(AIAgentType.PET)) == true || agent.isPet() == true || agent.isNecroPet() == true) { + damage = calculatePetDamage(agent); + } else if (agent.isPlayerGuard() == true) { + //damage = calculateGuardDamage(agent); + damage = calculateMobDamage(agent); + } else if (agent.getLevel() > 80) { + damage = calculateEpicDamage(agent); + } else { + damage = calculateMobDamage(agent); + } + if (AbstractWorldObject.IsAbstractCharacter(target)) { + if (((AbstractCharacter) target).isSit()) { + damage *= 2.5f; //increase damage if sitting + } + return (int) (((AbstractCharacter) target).getResists().getResistedDamage(agent, (AbstractCharacter) target, dt, damage, 0)); + } + if (target.getObjectType() == GameObjectType.Building) { + Building building = (Building) target; + Resists resists = building.getResists(); + return (int) ((damage * (1 - (resists.getResist(dt, 0) / 100)))); + } + return damage; + } + + public static DamageType getDamageType(Mob agent) { + DamageType dt = DamageType.Crush; + if (agent.getEquip().get(1) != null) { + return agent.getEquip().get(1).getItemBase().getDamageType(); + } + if (agent.getEquip().get(2) != null && agent.getEquip().get(2).getItemBase().isShield() == false) { + return agent.getEquip().get(2).getItemBase().getDamageType(); + } + return dt; + } + + public static int calculatePetDamage(Mob agent) { + //damage calc for pet + float range; + float damage; + float min = 40; + float max = 60; + float dmgMultiplier = 1 + agent.getBonuses().getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None); + double minDmg = getMinDmg(min, agent, null); + double maxDmg = getMaxDmg(max, agent, null); + dmgMultiplier += agent.getLevel() * 0.1f; + range = (float) (maxDmg - minDmg); + damage = min + ((ThreadLocalRandom.current().nextFloat() * range) + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + return (int) (damage * dmgMultiplier); + } + + public static int calculateGuardDamage(Mob agent) { + //damage calc for guard + ItemBase weapon = agent.getEquip().get(1).getItemBase(); + AbstractWorldObject target = agent.getCombatTarget(); + + float dmgMultiplier = 1 + agent.getBonuses().getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None); + + double minDmg = weapon.getMinDamage(); + double maxDmg = weapon.getMaxDamage(); + double min = getMinDmg(minDmg, agent, weapon); + double max = getMaxDmg(maxDmg, agent, weapon); + + DamageType dt = weapon.getDamageType(); + + double range = max - min; + double damage = min + ((ThreadLocalRandom.current().nextFloat() * range) + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + + if (AbstractWorldObject.IsAbstractCharacter(target)) + if (((AbstractCharacter) target).isSit()) + damage *= 2.5f; //increase damage if sitting + if (AbstractWorldObject.IsAbstractCharacter(target)) + return (int) (((AbstractCharacter) target).getResists().getResistedDamage(agent, (AbstractCharacter) target, dt, (float) damage, 0) * dmgMultiplier); + return 0; + } + + public static int calculateEpicDamage(Mob agent) { + //handle r8 mob damage + DamageType dt = DamageType.Crush; + AbstractWorldObject target = agent.getCombatTarget(); + float dmgMultiplier = 1 + agent.getBonuses().getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None); + double min = agent.getMinDamageHandOne(); + double max = agent.getMaxDamageHandOne(); + if (agent.getEquip().get(1) != null) { + if (agent.getEquip().get(1).getItemBase() != null) { + dt = agent.getEquip().get(1).getItemBase().getDamageType(); + min = agent.getMinDamageHandOne(); + max = agent.getMaxDamageHandOne(); + } else if (agent.getEquip().get(2).getItemBase() != null && agent.getEquip().get(2).getItemBase().isShield() == false) { + dt = agent.getEquip().get(2).getItemBase().getDamageType(); + min = agent.getMinDamageHandTwo(); + max = agent.getMaxDamageHandTwo(); + } + } + + + double range = max - min; + double damage = min + ((ThreadLocalRandom.current().nextFloat() * range) + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + return (int) (((AbstractCharacter) target).getResists().getResistedDamage(agent, (AbstractCharacter) target, dt, (float) damage, 0) * dmgMultiplier); + } + + public static int calculateMobDamage(Mob agent) { + ItemBase weapon = null; + double minDmg; + double maxDmg; + DamageType dt; + + //main hand or offhand damage + + if (agent.getEquip().get(1) != null) + weapon = agent.getEquip().get(1).getItemBase(); + else if (agent.getEquip().get(2) != null) + weapon = agent.getEquip().get(2).getItemBase(); + + if (weapon != null) { + minDmg = getMinDmg(weapon.getMinDamage(), agent, weapon); + maxDmg = getMaxDmg(weapon.getMaxDamage(), agent, weapon); + dt = weapon.getDamageType(); + } else { + minDmg = agent.getMobBase().getDamageMin(); + maxDmg = agent.getMobBase().getDamageMax(); + dt = DamageType.Crush; + } + + AbstractWorldObject target = agent.getCombatTarget(); + + float dmgMultiplier = 1 + agent.getBonuses().getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None); + double range = maxDmg - minDmg; + double damage = minDmg + ((ThreadLocalRandom.current().nextFloat() * range) + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + + if (AbstractWorldObject.IsAbstractCharacter(target)) + if (((AbstractCharacter) target).isSit()) + damage *= 2.5f; //increase damage if sitting + if (AbstractWorldObject.IsAbstractCharacter(target)) + return (int) (((AbstractCharacter) target).getResists().getResistedDamage(agent, (AbstractCharacter) target, dt, (float) damage, 0) * dmgMultiplier); + return 0; + } + + public static double getMinDmg(double min, Mob agent, ItemBase weapon) { + + int primary = agent.getStatStrCurrent(); + int secondary = agent.getStatDexCurrent(); + int focusLevel = 0; + int masteryLevel = 0; + + if (weapon != null) { + if (weapon.isStrBased() == true) { + primary = agent.getStatStrCurrent(); + secondary = agent.getStatDexCurrent(); + } else { + primary = agent.getStatDexCurrent(); + secondary = agent.getStatStrCurrent(); + if (agent.getSkills().containsKey(weapon.getSkillRequired())) { + focusLevel = (int) agent.getSkills().get(weapon.getSkillRequired()).getModifiedAmount(); + } + if (agent.getSkills().containsKey(weapon.getMastery())) { + masteryLevel = (int) agent.getSkills().get(weapon.getMastery()).getModifiedAmount(); + } + } + } + return min * (pow(0.0048 * primary + .049 * (primary - 0.75), 0.5) + pow(0.0066 * secondary + 0.064 * (secondary - 0.75), 0.5) + +0.01 * (focusLevel + masteryLevel)); + } + + public static double getMaxDmg(double max, Mob agent, ItemBase weapon) { + + int primary = agent.getStatStrCurrent(); + int secondary = agent.getStatDexCurrent(); + int focusLevel = 0; + int masteryLevel = 0; + + if (weapon != null) { + + if (weapon.isStrBased() == true) { + primary = agent.getStatStrCurrent(); + secondary = agent.getStatDexCurrent(); + } else { + primary = agent.getStatDexCurrent(); + secondary = agent.getStatStrCurrent(); + } + + if (agent.getSkills().containsKey(weapon.getSkillRequired())) + focusLevel = (int) agent.getSkills().get(weapon.getSkillRequired()).getModifiedAmount(); + + if (agent.getSkills().containsKey(weapon.getSkillRequired())) + masteryLevel = (int) agent.getSkills().get(weapon.getMastery()).getModifiedAmount(); + + } + return max * (pow(0.0124 * primary + 0.118 * (primary - 0.75), 0.5) + pow(0.0022 * secondary + 0.028 * (secondary - 0.75), 0.5) + 0.0075 * (focusLevel + masteryLevel)); + } + +} diff --git a/src/engine/mobileAI/utilities/MovementUtilities.java b/src/engine/mobileAI/utilities/MovementUtilities.java new file mode 100644 index 00000000..85affdca --- /dev/null +++ b/src/engine/mobileAI/utilities/MovementUtilities.java @@ -0,0 +1,295 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.mobileAI.utilities; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.mobileAI.Threads.MobAIThread; +import engine.exception.MsgSendException; +import engine.gameManager.MovementManager; +import engine.math.Vector3fImmutable; +import engine.net.client.msg.MoveToPointMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; +import static engine.math.FastMath.sqrt; + +public class MovementUtilities { + + + public static boolean inRangeOfBindLocation(Mob agent) { + + + if (agent.isPlayerGuard()) { + + Mob guardCaptain = null; + if (agent.getContract() != null) + guardCaptain = agent; + else + guardCaptain = (Mob) agent.npcOwner; + + if (guardCaptain != null) { + Building barracks = guardCaptain.building; + + if (barracks != null) { + City city = barracks.getCity(); + + if (city != null) { + Building tol = city.getTOL(); + + //Guards recall distance = 814. + if (tol != null) { + return !(agent.getLoc().distanceSquared2D(tol.getLoc()) > sqr(Enum.CityBoundsType.ZONE.extents)); + } + + } + } + } + + return true; + } + + Vector3fImmutable sl = new Vector3fImmutable(agent.getLoc().getX(), 0, agent.getLoc().getZ()); + Vector3fImmutable tl = new Vector3fImmutable(agent.getTrueBindLoc().x, 0, agent.getTrueBindLoc().z); + + float distanceSquaredToTarget = sl.distanceSquared2D(tl); //distance to center of target + float zoneRange = 250; + + if (agent.getParentZone() != null) { + if (agent.getParentZone().getBounds() != null) + zoneRange = agent.getParentZone().getBounds().getHalfExtents().x * 2; + } + + if (zoneRange > 300) + zoneRange = 300; + + if (agent.getSpawnRadius() > zoneRange) + zoneRange = agent.getSpawnRadius(); + + + return distanceSquaredToTarget < sqr(MobAIThread.AI_DROP_AGGRO_RANGE + zoneRange); + + } + + public static boolean inRangeToAggro(Mob agent, PlayerCharacter target) { + + Vector3fImmutable sl = agent.getLoc(); + Vector3fImmutable tl = target.getLoc(); + + float distanceSquaredToTarget = sl.distanceSquared2D(tl) - sqr(agent.calcHitBox() + target.calcHitBox()); //distance to center of target + float range = MobAIThread.AI_BASE_AGGRO_RANGE; + + if (agent.isPlayerGuard()) + range = 150; + + return distanceSquaredToTarget < sqr(range); + + } + + public static boolean inRangeDropAggro(Mob agent, AbstractCharacter target) { + + Vector3fImmutable sl = agent.getLoc(); + Vector3fImmutable tl = target.getLoc(); + + float distanceSquaredToTarget = sl.distanceSquared2D(tl) - sqr(agent.calcHitBox() + target.calcHitBox()); //distance to center of target + + float range = agent.getRange() + 150; + + if (range > 200) + range = 200; + + + return distanceSquaredToTarget < sqr(range); + + } + + public static Vector3fImmutable GetMoveLocation(Mob aiAgent, AbstractCharacter aggroTarget) { + + // Player isnt moving and neither is mob. Just return + // the mobile's current location. Ain't goin nowhere! + // *** Refactor: Check to ensure methods calling us + // all don't sent move messages when not moving. + + if ((aggroTarget.isMoving() == false)) + return aggroTarget.getLoc(); + + if (aggroTarget.getEndLoc().x != 0) { + + float aggroTargetDistanceSquared = aggroTarget.getLoc().distanceSquared2D(aggroTarget.getEndLoc()); + float aiAgentDistanceSquared = aiAgent.getLoc().distanceSquared2D(aggroTarget.getEndLoc()); + + if (aiAgentDistanceSquared >= aggroTargetDistanceSquared) + return aggroTarget.getEndLoc(); + else { + float distanceToMove = sqrt(aggroTargetDistanceSquared + aiAgentDistanceSquared) * .5f; + + return aggroTarget.getFaceDir().scaleAdd(distanceToMove, aggroTarget.getLoc()); + + } + } + + // One of us is moving so let's calculate our destination loc for this + // simulation frame. We will simply project our position onto the + // character's movement vector and return the closest point. + + return aiAgent.getLoc().ClosestPointOnLine(aggroTarget.getLoc(), aggroTarget.getEndLoc()); + } + + public static void moveToLocation(Mob agent, Vector3fImmutable newLocation, float offset) { + try { + + //don't move farther than 30 units from player. + if (offset > 30) + offset = 30; + Vector3fImmutable newLoc = Vector3fImmutable.getRandomPointInCircle(newLocation, offset); + + + agent.setFaceDir(newLoc.subtract2D(agent.getLoc()).normalize()); + + aiMove(agent, newLoc, false); + } catch (Exception e) { + Logger.error(e.toString()); + } + } + + + public static boolean canMove(Mob agent) { + if (agent.getMobBase() != null && Enum.MobFlagType.SENTINEL.elementOf(agent.getMobBase().getFlags())) + return false; + + return (agent.isAlive() && !agent.getBonuses().getBool(ModType.Stunned, SourceType.None) && !agent.getBonuses().getBool(ModType.CannotMove, SourceType.None)); + } + + public static Vector3fImmutable randomPatrolLocation(Mob agent, Vector3fImmutable center, float radius) { + + //Determing where I want to move. + return new Vector3fImmutable((center.x - radius) + ((ThreadLocalRandom.current().nextFloat() + .1f * 2) * radius), + center.y, + (center.z - radius) + ((ThreadLocalRandom.current().nextFloat() + .1f * 2) * radius)); + } + + public static Long estimateMovementTime(Mob agent) { + if (agent.getEndLoc().x == 0 && agent.getEndLoc().y == 0) + return 0L; + + return (long) ((agent.getLoc().distance2D(agent.getEndLoc()) * 1000) / agent.getSpeed()); + } + + public static void aiMove(Mob agent, Vector3fImmutable vect, boolean isWalking) { + + //update our walk/run state. + if (isWalking && !agent.isWalk()) { + agent.setWalkMode(true); + MovementManager.sendRWSSMsg(agent); + } else if (!isWalking && agent.isWalk()) { + agent.setWalkMode(false); + MovementManager.sendRWSSMsg(agent); + } + + MoveToPointMsg msg = new MoveToPointMsg(); + + +// Regions currentRegion = Mob.InsideBuildingRegion(agent); +// +// if (currentRegion != null){ +// +// +// if (currentRegion.isGroundLevel()){ +// agent.setInBuilding(0); +// agent.setInFloorID(-1); +// }else{ +// agent.setInBuilding(currentRegion.getLevel()); +// agent.setInFloorID(currentRegion.getRoom()); +// } +// }else{ +// agent.setInBuilding(-1); +// agent.setInFloorID(-1); +// agent.setInBuildingID(0); +// } +// agent.setLastRegion(currentRegion); + + + Vector3fImmutable startLoc = null; + Vector3fImmutable endLoc = null; + +// if (agent.getLastRegion() != null){ +// Building inBuilding = Building.getBuildingFromCache(agent.getInBuildingID()); +// if (inBuilding != null){ +// startLoc = ZoneManager.convertWorldToLocal(inBuilding, agent.getLoc()); +// endLoc = ZoneManager.convertWorldToLocal(inBuilding, vect); +// } +// }else{ +// agent.setBuildingID(0); +// agent.setInBuildingID(0); +// startLoc = agent.getLoc(); +// endLoc = vect; +// } + + startLoc = agent.getLoc(); + endLoc = vect; + + msg.setSourceType(GameObjectType.Mob.ordinal()); + msg.setSourceID(agent.getObjectUUID()); + msg.setStartCoord(startLoc); + msg.setEndCoord(endLoc); + msg.setUnknown01(-1); + msg.setInBuilding(-1); + msg.setTargetType(0); + msg.setTargetID(0); + + + try { + MovementManager.movement(msg, agent); + } catch (MsgSendException e) { + // TODO Figure out how we want to handle the msg send exception + e.printStackTrace(); + } + } + + public static Vector3fImmutable GetDestinationToCharacter(Mob aiAgent, AbstractCharacter character) { + + if (!character.isMoving()) + return character.getLoc(); + + + float agentDistanceEndLoc = aiAgent.getLoc().distanceSquared2D(character.getEndLoc()); + float characterDistanceEndLoc = character.getLoc().distanceSquared2D(character.getEndLoc()); + + if (agentDistanceEndLoc > characterDistanceEndLoc) + return character.getEndLoc(); + + return character.getLoc(); + } + + public static boolean updateMovementToCharacter(Mob aiAgent, AbstractCharacter aggroTarget) { + + if (aiAgent.destination.equals(Vector3fImmutable.ZERO)) + return true; + + if (!aiAgent.isMoving()) + return true; + + + if (aggroTarget.isMoving()) { + return !aiAgent.destination.equals(aggroTarget.getEndLoc()) && !aiAgent.destination.equals(aggroTarget.getLoc()); + } else { + if (aiAgent.destination.equals(aggroTarget.getLoc())) + return false; + } + + return false; + } + +} diff --git a/src/engine/net/AbstractConnection.java b/src/engine/net/AbstractConnection.java new file mode 100644 index 00000000..8fb86d11 --- /dev/null +++ b/src/engine/net/AbstractConnection.java @@ -0,0 +1,412 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.job.JobManager; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import org.pmw.tinylog.Logger; + +import java.io.IOException; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SocketChannel; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +public abstract class AbstractConnection implements + NetMsgHandler { + + protected static final long SOCKET_FULL_WRITE_DELAY = 50L; //wait one second to write on full socket + private static final String error01 = "A byte buffer is being free()ed that is not of the size stored in ByteBufferPool. Size: %1%"; + private static final String error02 = "(IP=%1%): Socket has reached the maximum outbound buffer length of %2%. Moving on while we wait for data to be sent."; + private static final int OPCODEHASH = 1016; //Opcodes are unique modulo this number as of 11/21/10 + protected final SocketChannel sockChan; + protected final AbstractConnectionManager connMan; + protected final NetMsgFactory factory; + protected final AtomicBoolean execTask = new AtomicBoolean(false); + protected final ReentrantLock writeLock = new ReentrantLock(); + protected final ReentrantLock readLock = new ReentrantLock(); + protected long lastMsgTime = System.currentTimeMillis(); + protected long lastKeepAliveTime = System.currentTimeMillis(); + protected long lastOpcode = -1; + protected ConcurrentLinkedQueue outbox = new ConcurrentLinkedQueue<>(); + protected ByteBuffer outBuf = null; + protected ConcurrentHashMap cacheList; + protected long nextWriteTime = 0L; + + //Opcode tracking + protected Protocol lastProtocol = Protocol.NONE; + + public AbstractConnection(AbstractConnectionManager connMan, + SocketChannel sockChan, boolean clientMode) { + this.connMan = connMan; + this.sockChan = sockChan; + this.factory = new NetMsgFactory(this); + + } + + public void disconnect() { + try { + this.sockChan.close(); + } catch (IOException e) { + Logger.error(e.toString()); + } + } + + /** + * Serializes AbstractNetMsg msg into a ByteBuffer, then queues that + * ByteBuffer for sending on this AbstractConnection's SocketChannel. + * + * @param msg - AbstractNetMsg to be sent. + * @return boolean status if queue is successful or not. On false return, + * the field lastError will be set. + */ + public final boolean sendMsg(AbstractNetMsg msg) { + // Logger.info("Send: " + msg.getSimpleClassName()); + try { + return this._sendMsg(msg); + + } catch (Exception e) { // Catch-all + Logger.error(e); + return false; + } + } + + /** + * Serializes AbstractNetMsg msg into a ByteBuffer, then queues that + * ByteBuffer for sending on this AbstractConnection's SocketChannel. This + * internal function is required to be overridden by subclasses. + * + * @param msg - AbstractNetMsg to be sent. + * @return boolean status if queue is successful or not. On false return, + * the field lastError will be set. + */ + protected abstract boolean _sendMsg(AbstractNetMsg msg); + + /** + * Queues ByteBuffer bb for sending on this AbstractConnection's + * SocketChannel. + * + * @param bb - ByteBuffer to be sent. + * @return boolean status if queue is successful or not. On false return, + * the field lastError will be set. + */ + public final boolean sendBB(ByteBuffer bb) { + + if (bb == null) + return false; + try { + return this._sendBB(bb); + } catch (Exception e) { // Catch-all + Logger.error(e); + e.printStackTrace(); + return false; + } + } + + /** + * Queues ByteBuffer bb for sending on this AbstractConnection's + * SocketChannel. This internal function is designed to be overrideable by + * subclasses if needed. + * + * @param bb - ByteBuffer to be sent. + * @return boolean status if queue is successful or not. On false return, + * the field lastError will be set. + */ + protected boolean _sendBB(ByteBuffer bb) { + this.outbox.add(bb); + this.connMan.sendStart(this.sockChan); + return true; + } + + /** + * Move data off the socketChannel's buffer and into the Connection's + * internal NetMsgFactory. + */ + // FIXME the int return value on this means nothing! Clean it up! + protected int read() { + + if (readLock.tryLock()) + try { + + if (this.sockChan.isOpen() == false) { + Logger.info("Sock channel closed. Disconnecting " + this.getRemoteAddressAndPortAsString()); + this.disconnect(); + return 0; + } + + // get socket buffer sized buffer from multipool + ByteBuffer bb = Network.byteBufferPool.getBuffer(16); + + int totalBytesRead = 0; + int lastRead; + do { + try { + bb.clear(); + lastRead = this.sockChan.read(bb); + + // On EOF on the SocketChannel, disconnect. + if (lastRead <= -1) { + Logger.info(" EOF on Socket Channel " + this.getRemoteAddressAndPortAsString()); + this.disconnect(); + break; + } + + if (lastRead == 0) + continue; + + synchronized (this.factory) { + this.factory.addData(bb); + } + this.checkInternalFactory(); + + totalBytesRead += lastRead; + + } catch (NotYetConnectedException e) { + Logger.error(e.toString()); + break; + + } catch (ClosedChannelException e) { + Logger.error(e.toString()); + this.disconnect(); + break; + + } catch (IOException e) { + if (!e.getMessage().startsWith( + "An existing connection was forcibly closed")) + Logger.error(e.toString()); + this.disconnect(); + break; + + } catch (Exception e) { + Logger.error(e.toString()); + break; + } + } while (lastRead > 0); + + // put buffer back into multipool + Network.byteBufferPool.putBuffer(bb); + + this.checkInternalFactory(); + return totalBytesRead; + + } finally { + readLock.unlock(); + } + else { + Logger.debug("Another thread already has a read lock! Skipping."); + return 0; + } + } + + /** + * Move data off the Connection's buffer and into the SocketChannel's + * Buffer. + */ + protected boolean writeAll() { + + //intentional delay if socket is full. Give the socket a chance to clear out. + if (System.currentTimeMillis() < this.nextWriteTime) + return false; + + if (writeLock.tryLock()) + try { + String s = ""; + int written = 0; + + boolean allSentOK = true, bufferDoubled = false; + + while (this.outbox.peek() != null) { + ByteBuffer bb = this.outbox.peek(); + + int toSend = bb.position(); + + try { + bb.flip(); + written = this.sockChan.write(bb); + + // Logger.debug("Using a BB with cap of: " + bb.capacity() + // + ". toSend: " + toSend + ", written: " + written); + if (written != toSend) + bb.compact(); // Clean up in case not all gets sent + + if (toSend == written) { + // Actually remove it from the queue + this.outbox.poll(); + + // Pool it + Network.byteBufferPool.putBuffer(bb); + continue; + + } else if (written == 0) { + //Socket full, let's delay the next write on socket. + this.nextWriteTime = System.currentTimeMillis() + AbstractConnection.SOCKET_FULL_WRITE_DELAY; + + int currentBufferSize = this.sockChan.socket() + .getSendBufferSize(); + + if (!bufferDoubled && currentBufferSize <= Network.INITIAL_SOCKET_BUFFER_SIZE) { + this.doubleSocketSendBufferSize(); + bufferDoubled = true; + } else { + + // s = error02; + // s = s.replaceAll("%1%", this + // .getRemoteAddressAndPortAsString() + ":" + + // this.printLastOpcodes()); + // s = s.replaceAll("%2%", currentBufferSize + ""); + // this.warn(s); + + allSentOK = false; + break; + } + } + + } catch (ClosedChannelException e) { + // Catches AsynchronousCloseException, + // and ClosedByInterruptException + Logger.error(e); + break; + + } catch (Exception e) { + // Catches NotYetConnectedException + // and IOException + Logger.error(e); + this.disconnect(); + break; + } + + } + return allSentOK; + } finally { + writeLock.unlock(); + } + else { + Logger.debug("Another thread already has a write lock! Skipping."); + return false; + } + } + + private boolean doubleSocketSendBufferSize() { + String s; + try { + int currentSize = this.sockChan.socket().getSendBufferSize(); + int newSize = currentSize << 1; + + this.sockChan.socket().setSendBufferSize(newSize); + + // s = "(IP=" + this.getRemoteAddressAndPortAsString() + "): "; + // s += this.printLastOpcodes(); + // s += "Socket has reached the maximum outbound buffer length of "; + // s += currentSize + ". Attempting to double the outbound buffer size."; + // + // this.warn(s); + return true; + } catch (SocketException e) { + Logger.error(e.toString()); + return false; + } + } + + public boolean isConnected() { + return this.sockChan.isConnected(); + } + + public boolean isOpen() { + return this.sockChan.isOpen(); + } + + /* + * Getters n Setters + */ + public SocketChannel getSocketChannel() { + return this.sockChan; + } + + public final void checkInternalFactory() { + CheckNetMsgFactoryJob j = new CheckNetMsgFactoryJob(this); + JobManager.getInstance().submitJob(j); + } + + public NetMsgFactory getFactory() { + return factory; + } + + public String getRemoteAddressAndPortAsString() { + String out = ""; + + if (this.sockChan == null) + out += "NotConnected"; + else if (this.sockChan.socket() == null) + out += "NotConnected"; + else if (this.sockChan.socket().getRemoteSocketAddress() == null) + out += "NotConnected"; + else + out += this.sockChan.socket().getRemoteSocketAddress().toString(); + + return out; + } + + public String getLocalAddressAndPortAsString() { + String out = ""; + + if (this.sockChan == null) + out += "NotConnected"; + else if (this.sockChan.socket() == null) + out += "NotConnected"; + else if (this.sockChan.socket().getRemoteSocketAddress() == null) + out += "NotConnected"; + else { + out += this.sockChan.socket().getLocalSocketAddress().toString(); + out += ":"; + out += this.sockChan.socket().getLocalPort(); + } + + return out; + } + + /** + * Gives the Connection a chance to act on a msg prior to sending it to the + * provided NetMsgHandler + */ + @Override + public abstract boolean handleClientMsg(ClientNetMsg msg); + + protected long getLastMsgTime() { + return this.lastMsgTime; + } + + protected void setLastMsgTime() { + // TODO Consider making this a static to latest system time + this.lastMsgTime = System.currentTimeMillis(); + } + + protected long getLastKeepAliveTime() { + return this.lastKeepAliveTime; + } + + protected void setLastKeepAliveTime() { + this.lastKeepAliveTime = System.currentTimeMillis(); + } + + public long getLastOpcode() { + return lastOpcode; + } + + public void setLastOpcode(long lastOpcode) { + this.lastOpcode = lastOpcode; + } + +} diff --git a/src/engine/net/AbstractConnectionManager.java b/src/engine/net/AbstractConnectionManager.java new file mode 100644 index 00000000..0810fbb7 --- /dev/null +++ b/src/engine/net/AbstractConnectionManager.java @@ -0,0 +1,704 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.core.ControlledRunnable; +import engine.job.AbstractJob; +import engine.job.JobManager; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.io.IOException; +import java.net.*; +import java.nio.channels.*; +import java.nio.channels.spi.SelectorProvider; +import java.util.Iterator; +import java.util.concurrent.ConcurrentLinkedQueue; + +public abstract class AbstractConnectionManager extends ControlledRunnable { + + private final Selector selector; + private final ServerSocketChannel listenChannel; + private final ConcurrentLinkedQueue chngReqs = new ConcurrentLinkedQueue<>(); + /** + * Removes the mapping that contains the key 'sockChan' + * + * @param sockChan + */ + private long lastAuditTime = 0; + + /* + * + */ + public AbstractConnectionManager(String nodeName, InetAddress hostAddress, + int port) throws IOException { + super(nodeName); + + this.selector = SelectorProvider.provider().openSelector(); + + // Create a new non-blocking Server channel + this.listenChannel = ServerSocketChannel.open(); + this.listenChannel.configureBlocking(false); + + // Bind + InetSocketAddress isa = new InetSocketAddress(hostAddress, port); + this.listenChannel.socket().bind(isa); + + Logger.info(this.getLocalNodeName() + " Configured to listen: " + + isa.getAddress().toString() + ':' + port); + + // register an interest in Accepting new connections. + SelectionKey sk = this.listenChannel.register(this.selector, SelectionKey.OP_ACCEPT); + sk.attach(this); + } + + /* + * ControlledRunnable implementations + */ + @Override + protected void _startup() { + } + + @Override + protected void _shutdown() { + this.selector.wakeup(); + this.disconnectAll(); + this.selector.wakeup(); + } + + @Override + protected boolean _preRun() { + this.runStatus = true; + return true; + } + + @Override + protected boolean _Run() { + while (this.runCmd) { + try { + this.runLoopHook(); + + this.processChangeRequests(); + this.auditSocketChannelToConnectionMap(); + this.selector.select(250L); + this.processNewEvents(); + + } catch (Exception e) { + Logger.error(e.toString()); + } + } + return true; + } + + @Override + protected boolean _postRun() { + + this.runStatus = false; + + this.disconnectAll(); + + try { + this.selector.close(); + } catch (IOException e) { + Logger.error(e.toString()); + } + + return true; + } + + /** + * Hook for subclasses to use. + */ + protected void runLoopHook() { + } + + /* + * Accept / New Connection FNs + */ + private AbstractConnection acceptNewConnection(final SelectionKey key) + throws IOException { + + this.preAcceptNewConnectionHook(key); + + // Cancel incoming connections if server isn't set to listen + if (this.listenChannel == null || this.listenChannel.isOpen() == false) { + key.cancel(); + return null; + } + + // For an accept to be pending, the key contains a reference to the + // ServerSocketChannel + ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); + + // Get SocketChannel + SocketChannel sockChan = ssc.accept(); + sockChan.configureBlocking(false); + + //Configure the Socket + Socket socket = sockChan.socket(); + socket.setSendBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE); + socket.setReceiveBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE); + socket.setTcpNoDelay(MBServerStatics.TCP_NO_DELAY_DEFAULT); + + //Register with the selector + SelectionKey sk = sockChan.register(this.selector, SelectionKey.OP_READ); + if (sk == null) { + Logger.error("FIX ME! NULL SELECTION KEY!"); + return null; + } + + //Initialize Connection + AbstractConnection ac = this.getNewIncomingConnection(sockChan); + sk.attach(ac); + + this.postAcceptNewConnectionHook(ac); + return ac; + } + + /** + * Hook for subclasses to use. + * + * @param key + */ + protected void preAcceptNewConnectionHook(SelectionKey key) { + } + + /** + * Hook for subclasses to use. + * + * @param ac + */ + protected void postAcceptNewConnectionHook(AbstractConnection ac) { + } + + protected abstract AbstractConnection getNewIncomingConnection(SocketChannel sockChan); + + protected abstract AbstractConnection getNewOutgoingConnection(SocketChannel sockChan); + + /* + * Disconnect / Destroy Connection FNs + */ + protected boolean disconnect(final SelectionKey key) { + + this.disconnect((AbstractConnection) key.attachment()); + + key.attach(null); + key.cancel(); + return key.isValid(); + } + + protected boolean disconnect(final AbstractConnection c) { + if (c == null) + return false; + + c.disconnect(); + + return c.getSocketChannel().isConnected(); + } + + /* + * Data IO + */ + + /* + * WRITE SEQUENCE + */ + + protected void disconnectAll() { + synchronized (this.selector.keys()) { + for (SelectionKey sk : this.selector.keys()) { + if (sk.channel() instanceof SocketChannel) + disconnect(sk); + } + } + } + + /** + * Submits a request to set this Connection to WRITE mode. + */ + protected void sendStart(final SocketChannel sockChan) { + synchronized (this.chngReqs) { + // Indicate we want the interest ops set changed + this.chngReqs.add(new ChangeRequest(sockChan, ChangeType.CHANGEOPS, SelectionKey.OP_WRITE)); + } + + this.selector.wakeup(); + } + + /* + * READ SEQUENCE + */ + + /** + * @param key + * @return Boolean indication emit success. + */ + protected boolean sendFinish(final SelectionKey key) { + SocketChannel sockChan = (SocketChannel) key.channel(); + + // Check to see if the SocketChannel the selector offered up + // is null. + if (sockChan == null) { + Logger.error(": null sockChannel."); + this.disconnect(key); + return false; + } + + AbstractConnection c = (AbstractConnection) key.attachment(); + + if (c == null) { + Logger.error(": null Connection."); + this.disconnect(key); + return false; + } + + // long startTime = System.currentTimeMillis(); + boolean allSent = c.writeAll(); + + // if ((System.currentTimeMillis() - startTime) > 20) + // this.logDirectWARNING(c.getRemoteAddressAndPortAsString() + " took " + (System.currentTimeMillis() - startTime) + "ms to handle!"); + + // If all was written, switch back to Read Mode. + if (allSent || !c.isConnected()) { + + // Indicate we want the interest ops set changed + ChangeRequest chReq = new ChangeRequest(c.getSocketChannel(), ChangeType.CHANGEOPS, SelectionKey.OP_READ); + synchronized (this.chngReqs) { + this.chngReqs.add(chReq); + } + + this.selector.wakeup(); + } + return true; + } + + /** + * @param key + * @return Boolean indication of success. + */ + private boolean receive(final SelectionKey key) { + SocketChannel sockChan = (SocketChannel) key.channel(); + + // Check to see if the SocketChannel the selector offered up + // is null. + if (sockChan == null) { + Logger.error("null sockChannel."); + this.disconnect(key); + return false; + } + + AbstractConnection c = (AbstractConnection) key.attachment(); + + if (c == null) { + Logger.error("null Connection."); + this.disconnect(key); + return false; + } + + c.read(); + + return true; + } + + /* + * Main Loop And Loop Controls + */ + private void processChangeRequests() { + SelectionKey selKey = null; + ChangeRequest sccr = null; + ChangeType change = null; + SocketChannel sockChan = null; + + synchronized (this.chngReqs) { + Iterator it = this.chngReqs.iterator(); + while (it.hasNext()) { + sccr = it.next(); + + if (sccr == null) { + it.remove(); + continue; + } + + change = sccr.getChangeType(); + sockChan = sccr.getSocketChannel(); + + switch (change) { + case CHANGEOPS: + selKey = sockChan.keyFor(this.selector); + + if (selKey == null || selKey.isValid() == false) + continue; + + selKey.interestOps(sccr.getOps()); + break; + + case REGISTER: + try { + sockChan.register(this.selector, sccr.getOps()); + + } catch (ClosedChannelException e) { + // TODO Should a closed channel be logged or just + // cleaned up? + // Logger.error(this.getLocalNodeName(), e); + } + break; + } + } + this.chngReqs.clear(); + } + } + + private void processNewEvents() { + SelectionKey thisKey = null; + Iterator selectedKeys = null; + JobManager jm = JobManager.getInstance(); + + selectedKeys = this.selector.selectedKeys().iterator(); + + if (selectedKeys.hasNext() == false) + return; + + while (selectedKeys.hasNext()) { + thisKey = selectedKeys.next(); + + //To shake out any issues + if (thisKey.attachment() == null) + Logger.error("Found null attachment! PANIC!"); + + if (thisKey.attachment() instanceof AbstractConnection) + if (((AbstractConnection) thisKey.attachment()).execTask.get() == true) + continue; + + selectedKeys.remove(); + + try { + if (thisKey.isValid() == false) + break; // Changed from continue + else if (thisKey.isAcceptable()) + this.acceptNewConnection(thisKey); + else if (thisKey.isReadable()) + jm.submitJob(new ReadOperationHander(thisKey)); + else if (thisKey.isWritable()) + jm.submitJob(new WriteOperationHander(thisKey)); + else if (thisKey.isConnectable()) + this.finishConnectingTo(thisKey); + else + Logger.error("Unhandled keystate: " + thisKey.toString()); + } catch (CancelledKeyException cke) { + Logger.error(this.getLocalNodeName(), cke); + this.disconnect(thisKey); + + } catch (IOException e) { + Logger.error(this.getLocalNodeName(), e); + } + } + } + + protected void connectTo(String host, int port) { + try { + this.connectTo(InetAddress.getByName(host), port); + } catch (UnknownHostException e) { + Logger.error(this.getLocalNodeName(), e); + } + } + + protected void connectTo(InetAddress host, int port) { + try { + this.startConnectingTo(host, port); + this.selector.wakeup(); + } catch (IOException e) { + Logger.error(this.getLocalNodeName(), e); + } + } + + protected final void startConnectingTo(InetAddress host, int port) + throws IOException { + // Create a non-blocking socket channel + SocketChannel sockChan = SocketChannel.open(); + sockChan.configureBlocking(false); + sockChan.socket().setSendBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE); + sockChan.socket().setReceiveBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE); + + // Make a new Connection object + this.getNewOutgoingConnection(sockChan); + + // Kick off connection establishment + sockChan.connect(new InetSocketAddress(host, port)); + + synchronized (this.chngReqs) { + this.chngReqs.add(new ChangeRequest(sockChan, ChangeType.REGISTER, SelectionKey.OP_CONNECT)); + } + + this.selector.wakeup(); + } + + private void finishConnectingTo(SelectionKey key) throws IOException { + this.preFinishConnectingToHook(key); + + // Get sockChan + SocketChannel sockChan = (SocketChannel) key.channel(); + + // Get AbstractConnection + AbstractConnection ac = (AbstractConnection) key.attachment(); + + if (sockChan == null) { + Logger.error(this.getLocalNodeName(), "null socketChannel"); + this.disconnect(key); + return; + } + + if (ac == null) { + Logger.error(this.getLocalNodeName(), "null AbstractConnection"); + this.disconnect(key); + return; + } + + // Finish the connection. If the connection operation failed + // this will raise an IOException. + try { + sockChan.finishConnect(); + } catch (IOException e) { + if (e.getMessage().startsWith("Connection refused:") + || e.getMessage().startsWith( + "An existing connection was forcibly closed")) { + // eat this type of IOException + } else + Logger.error(this.getLocalNodeName(), e); + + // Cancel the channel's registration with our selector + key.cancel(); + return; + } + + Socket socket = sockChan.socket(); + Logger.debug("Connected to: " + + socket.getInetAddress() + ':' + + socket.getPort()); + + sockChan.configureBlocking(false); + sockChan.register(this.selector, SelectionKey.OP_READ); + + this.postFinishConnectingToHook(ac); + } + + /** + * Hook for subclasses to use. + * + * @param key + */ + protected void preFinishConnectingToHook(SelectionKey key) { + } + + /** + * Hook for subclasses to use. + * + * @param ac + */ + protected void postFinishConnectingToHook(AbstractConnection ac) { + } + + public final String getLocalNodeName() { + return this.getThreadName(); + } + + protected int auditSocketChannelToConnectionMap() { + long startTime = System.currentTimeMillis(); + int numberOfItemsToProcess = 0; + + if (lastAuditTime + MBServerStatics.TIMEOUT_CHECKS_TIMER_MS > startTime) + return -1; + + synchronized (this.selector.keys()) { + for (SelectionKey sk : this.selector.keys()) { + if (!(sk.channel() instanceof SocketChannel)) + continue; + + SocketChannel sockChan = (SocketChannel) sk.channel(); + AbstractConnection conn = (AbstractConnection) sk.attachment(); + + if (sockChan == null) + continue; + + if (!sockChan.isOpen()) { + numberOfItemsToProcess++; + Logger.info("sockChan closed. Disconnecting.."); + disconnect(sk); + continue; + } + + if (conn == null) { + numberOfItemsToProcess++; + Logger.info("Connection is null, Disconnecting."); + disconnect(sk); + continue; + } + + //removed keep alive timeout. Believe failmu used this for disconnecting players on force quit, but a closed socket will already disconnect. +// if (conn.getLastKeepAliveTime() + MBServerStatics.KEEPALIVE_TIMEOUT_MS < startTime) { +// numberOfItemsToProcess++; +// Logger.info("Keep alive Disconnecting " + conn.getRemoteAddressAndPortAsString()); +// conn.disconnect(); +// continue; +// } + + if (conn.getLastMsgTime() + MBServerStatics.AFK_TIMEOUT_MS < startTime) { + numberOfItemsToProcess++; + Logger.info("AFK TIMEOUT Disconnecting " + conn.getRemoteAddressAndPortAsString()); + conn.disconnect(); + } + } + } + + if (numberOfItemsToProcess != 0) + Logger.info("Cleaned " + + numberOfItemsToProcess + + " dead connections in " + + (System.currentTimeMillis() - startTime) + + " millis."); + + lastAuditTime = System.currentTimeMillis(); + return numberOfItemsToProcess; + } + + public int getConnectionSize() { + if (this.selector == null) + return -1; + if (this.selector.keys() == null) + return -1; + return this.selector.keys().size(); + } + + /** + * Returns the port on which this socket is listening. + * + * @return the port number to which this socket is listening or -1 if the + * socket is not bound yet. + */ + public int getListeningPort() { + if (this.listenChannel == null) + return -1; + if (this.listenChannel.socket() == null) + return -1; + return this.listenChannel.socket().getLocalPort(); + } + + /** + * Returns the address of the endpoint this socket is bound to, or null if + * it is not bound yet. + * + * @return a SocketAddress representing the local endpoint of this socket, + * or null if it is not bound yet. + */ + public SocketAddress getListeningAddress() { + if (this.listenChannel == null) + return null; + if (this.listenChannel.socket() == null) + return null; + return this.listenChannel.socket().getLocalSocketAddress(); + } + + /* + * + */ + protected static enum ChangeType { + + REGISTER, CHANGEOPS + } + + private class ChangeRequest { + + private final SocketChannel sockChan; + private final ChangeType changeType; + private final Integer ops; + + public ChangeRequest(SocketChannel sockChan, ChangeType changeType, + int ops) { + this.sockChan = sockChan; + this.changeType = changeType; + this.ops = ops; + } + + public SocketChannel getSocketChannel() { + synchronized (this.sockChan) { + return this.sockChan; + } + } + + public ChangeType getChangeType() { + synchronized (this.changeType) { + return this.changeType; + } + } + + public int getOps() { + synchronized (this.ops) { + return this.ops; + } + } + } + + private class ReadOperationHander extends AbstractJob { + + private final SelectionKey sk; + private final AbstractConnection ac; + private final boolean runStatus; + + public ReadOperationHander(final SelectionKey sk) { + this.sk = sk; + + if (sk.attachment() instanceof AbstractConnection) { + this.ac = (AbstractConnection) sk.attachment(); + this.runStatus = this.ac.execTask.compareAndSet(false, true); + } else { + this.ac = null; + this.runStatus = false; + Logger.error("Passed selection key did not have a corresponding Connection!(Read)"); + } + } + + @Override + protected void doJob() { + if (runStatus) { + this.ac.connMan.receive(sk); + this.ac.execTask.compareAndSet(true, false); + } + } + } + + private class WriteOperationHander extends AbstractJob { + + private final SelectionKey sk; + private final AbstractConnection ac; + private final boolean runStatus; + + public WriteOperationHander(final SelectionKey sk) { + this.sk = sk; + + if (sk.attachment() instanceof AbstractConnection) { + this.ac = (AbstractConnection) sk.attachment(); + this.runStatus = this.ac.execTask.compareAndSet(false, true); + } else { + this.runStatus = false; + this.ac = null; + Logger.error("Passed selection key did not have a corresponding Connection!(Write)"); + } + + } + + @Override + protected void doJob() { + if (runStatus) { + this.ac.connMan.sendFinish(sk); + this.ac.execTask.compareAndSet(true, false); + } + } + } + +} diff --git a/src/engine/net/AbstractNetMsg.java b/src/engine/net/AbstractNetMsg.java new file mode 100644 index 00000000..eb7e7095 --- /dev/null +++ b/src/engine/net/AbstractNetMsg.java @@ -0,0 +1,240 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.exception.SerializationException; +import engine.net.client.Protocol; +import engine.server.MBServerStatics; +import engine.util.StringUtils; +import org.pmw.tinylog.Logger; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class represents the NetMsgs set to/from the SBClient and in between + * MBServer Server Suite components. Note that since the NetMsgs sent to/from + * the SBClient do NOT include a MsgLen or DataLen parameter, special + * serialization/deserialization must be implemented. + */ +public abstract class AbstractNetMsg { + + private static ConcurrentHashMap stats = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH); + protected final Protocol protocolMsg; + private AbstractConnection origin; + + /** + * This is the general purpose constructor. + * + * @param protocolMsg + */ + protected AbstractNetMsg(Protocol protocolMsg) { + super(); + this.protocolMsg = protocolMsg; + } + + protected AbstractNetMsg(Protocol protocolMsg, AbstractConnection origin) { + super(); + this.protocolMsg = protocolMsg; + this.origin = origin; + } + + protected AbstractNetMsg(Protocol protocolMsg, AbstractNetMsg msg) { + super(); + this.protocolMsg = protocolMsg; + this.origin = msg.origin; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + * + * @param reader + */ + protected AbstractNetMsg(Protocol protocolMsg, AbstractConnection origin, + ByteBufferReader reader) { + this.protocolMsg = protocolMsg; + this.origin = origin; + + // Call the subclass specific deserializer + try { + this._deserialize(reader); + } catch (NullPointerException e) { + Logger.error(e); + } + } + + private static void allocHeader(ByteBufferWriter writer, int bytes) { + byte zero = 0; // prevents the int->byte cast + for (int h = 0; h < bytes; ++h) { + writer.put(zero); + } + } + + /** + * Deserializes the subclass specific items from the supplied + * ByteBufferReader + * + * @param reader + */ + protected abstract void _deserialize(ByteBufferReader reader); + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter + * + * @param writer + * @throws Exception + */ + protected abstract void _serialize(ByteBufferWriter writer) + throws SerializationException; + + /** + * Attempts to serialize this NetMsg into a ByteBuffer. ByteBuffer is + * obtained from a pool, so to retain max efficiency, the caller needs to + * return this BB to the pool. Header size and layout is entirely defined by + * the subclass of AbstractNetMsg + * + * @return a ByteBuffer + */ + public final ByteBuffer serialize() { + + NetMsgStat stat; + + if (!AbstractNetMsg.stats.containsKey(this.protocolMsg)) { + stat = new NetMsgStat(this.protocolMsg, this.getPowerOfTwoBufferSize()); + AbstractNetMsg.stats.put(this.protocolMsg, stat); + } else + stat = AbstractNetMsg.stats.get(this.protocolMsg); + int lowerPow = stat.getMax(); + int upperPow = lowerPow + 4; + + ByteBuffer bb = null; + + int startPos = 0; + ByteBufferWriter writer = null; + + for (int i = lowerPow; i < upperPow; ++i) { + + // get an appropriate sized BB from pool + + bb = Network.byteBufferPool.getBuffer(i); + + // Mark start position + + startPos = bb.position(); + + // Make a writer + + writer = new ByteBufferWriter(bb); // FIXME inefficient to + + // Set aside header here. + + AbstractNetMsg.allocHeader(writer, this.getHeaderSize()); + + // Now serialize the object's specifics + + try { + this._serialize(writer); + + //Serialize successful, update NetMsgStat + + stat.updateStat(i); + + } catch (BufferOverflowException boe) { + Logger.error("BufferSize PowerOfTwo: " + i + + " is too small for " + protocolMsg != null ? protocolMsg.name() : this.getClass().getName() + ", trying again with " + (i + 1)); + + //Return buffer. + + Network.byteBufferPool.putBuffer(bb); + continue; + + } catch (Exception e) { + + //Return buffer. + Logger.error(e); + e.printStackTrace(); + + Network.byteBufferPool.putBuffer(bb); + return null; + } + + // This shouldn't throw any errors since this part of the BB has + // already been allocated + + this.writeHeaderAt(startPos, writer); + return writer.getBb(); + } + + // If we get here, its not a successful serialization and lastError + // should be set + + return null; + } + + /** + * Function allows for setting other than default initial Buffer size for + * the Serializer. Override and return the size of the buffer in power of + * two bytes. + *

+ * Example, if you would like a buffer of 65535, then return 16 from this + * value since 2^16 = 65535 + * + * @return the power to raise two to. + */ + protected int getPowerOfTwoBufferSize() { + return (10); // 2^10 == 1024 + } + + /** + * Forces subclass to define how large (in bytes) the message's header is. + * + * @return the length (in bytes) of this message type's header. + */ + protected abstract int getHeaderSize(); + + /** + * Forces subclasses to implement how to write its own header into a + * byteBuffer + * + * @param startPos - starting position for the header write + * @param writer - ByteBufferWriter to write the header to. + */ + protected abstract void writeHeaderAt(int startPos, ByteBufferWriter writer); + + /** + * @return The protocolMsg of this Msg. + */ + public Protocol getProtocolMsg() { + return protocolMsg; + } + + /** + * @return The protocolMsg As a string. + */ + public String getOpcodeAsString() { + return StringUtils.toHexString(protocolMsg.opcode); + } + + /** + * @return the origin + */ + public AbstractConnection getOrigin() { + return origin; + } + + public void setOrigin(AbstractConnection conn) { + this.origin = conn; + } + +} diff --git a/src/engine/net/ByteBufferReader.java b/src/engine/net/ByteBufferReader.java new file mode 100644 index 00000000..10a77a8f --- /dev/null +++ b/src/engine/net/ByteBufferReader.java @@ -0,0 +1,372 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.util.ByteBufferUtils; +import engine.util.ByteUtils; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.UUID; + +public class ByteBufferReader { + + private final ByteBuffer bb; + private final boolean endianFlip; + + /** + * Helper class for getting structured information out of a ByteBuffer + * easily. ByteBuffer passed in is copied to an internal ByteBuffer. + * bbin must have the position attribute at the end of the + * data to be copied over, since bbin.flip() is called in this + * constructor. + * + * @param bbin + * @param endianFlip + */ + public ByteBufferReader(ByteBuffer bbin, boolean endianFlip) { + super(); + + // Copy supplied BB. + this.bb = ByteBuffer.allocate(bbin.position()); //FIXME Do we want to get this from pool? + bbin.flip(); + this.bb.put(bbin); + + // prepare bb for reading + this.bb.flip(); + + this.endianFlip = endianFlip; + } + + /* + * Getters + */ + + /** + * @return + * @see java.nio.ByteBuffer#get() + */ + public byte get() { + return bb.get(); + } + + public void get(byte[] ba) { + this.bb.get(ba); + } + + /** + * @return + * @see java.nio.ByteBuffer#getChar() + */ + public char getChar() { + char x = bb.getChar(); + if (this.endianFlip) { + x = Character.reverseBytes(x); + } + return x; + } + + /** + * @return + * @see java.nio.ByteBuffer#getDouble() + */ + public double getDouble() { + double x = 0; + if (this.endianFlip) { + x = bb.order(ByteOrder.LITTLE_ENDIAN).getDouble(); + bb.order(ByteOrder.BIG_ENDIAN); + } else { + x = bb.getDouble(); + } + return x; + } + + /** + * @return + * @see java.nio.ByteBuffer#getFloat() + */ + public float getFloat() { + float x = 0; + if (this.endianFlip) { + x = bb.order(ByteOrder.LITTLE_ENDIAN).getFloat(); + bb.order(ByteOrder.BIG_ENDIAN); + } else { + x = bb.getFloat(); + } + return x; + } + + /** + * @return + * @see java.nio.ByteBuffer#getInt() + */ + public int getInt() { + int x = bb.getInt(); + if (this.endianFlip) { + x = Integer.reverseBytes(x); + } + return x; + } + + /** + * @return + * @see java.nio.ByteBuffer#getLong() + */ + public long getLong() { + long x = bb.getLong(); + if (this.endianFlip) { + x = Long.reverseBytes(x); + } + return x; + } + + /** + * @return + * @see java.nio.ByteBuffer#getShort() + */ + public short getShort() { + short x = bb.getShort(); + if (this.endianFlip) { + x = Short.reverseBytes(x); + } + return x; + } + + public final String getString() { + if (this.endianFlip) { + return ByteBufferUtils.getString(this.bb, true, false); + } else { + return ByteBufferUtils.getString(this.bb, false, false); + } + } + + public final String getSmallString() { + if (this.endianFlip) { + return ByteBufferUtils.getString(this.bb, true, true); + } else { + return ByteBufferUtils.getString(this.bb, false, true); + } + } + + public final String getHexString() { + if (this.endianFlip) { + return ByteBufferUtils.getHexString(this.bb, true); + } else { + return ByteBufferUtils.getHexString(this.bb); + } + } + + public final String getUnicodeString() { + if (this.endianFlip) { + return ByteBufferUtils.getUnicodeString(this.bb, true); + } else { + return ByteBufferUtils.getUnicodeString(this.bb); + } + } + + public String get1ByteAsHexString() { + return getBytesAsHexStringCommon(new byte[1]); + } + + public String get2BytesAsHexString() { + return getBytesAsHexStringCommon(new byte[2]); + } + + public String get4BytesAsHexString() { + return getBytesAsHexStringCommon(new byte[4]); + } + + public String get8BytesAsHexString() { + return getBytesAsHexStringCommon(new byte[8]); + } + + private String getBytesAsHexStringCommon(byte[] ba) { + this.bb.get(ba); + if (this.endianFlip) { + return ByteUtils.byteArrayToStringHex(ByteUtils + .switchByteArrayEndianness(ba)); + } else { + return ByteUtils.byteArrayToStringHex(ba); + } + } + + public Vector3f getVector3f() { + Vector3f out = new Vector3f(); + if (this.endianFlip) { + out.x = Float + .intBitsToFloat(Integer.reverseBytes(this.bb.getInt())); + out.y = Float + .intBitsToFloat(Integer.reverseBytes(this.bb.getInt())); + out.z = Float + .intBitsToFloat(Integer.reverseBytes(this.bb.getInt())); + } else { + out.x = this.bb.getFloat(); + out.y = this.bb.getFloat(); + out.z = this.bb.getFloat(); + } + return out; + } + + public Vector3fImmutable getVector3fImmutable() { + Vector3fImmutable out; + if (this.endianFlip) { + out = new Vector3fImmutable(Float.intBitsToFloat(Integer + .reverseBytes(this.bb.getInt())), Float + .intBitsToFloat(Integer.reverseBytes(this.bb.getInt())), + Float + .intBitsToFloat(Integer.reverseBytes(this.bb + .getInt()))); + } else { + out = new Vector3fImmutable(this.bb.getFloat(), this.bb.getFloat(), + this.bb.getFloat()); + } + return out; + } + + public final UUID getUUID() { + final byte[] buffer = new byte[16]; + this.bb.get(buffer); + + long msb = 0; + long lsb = 0; + + for (int i = 0; i < 8; i++) { + msb = (msb << 8) | (buffer[i] & 0xff); + } + + for (int i = 8; i < 16; i++) { + lsb = (lsb << 8) | (buffer[i] & 0xff); + } + + return new UUID(msb, lsb); + } + + + /* + * Monitors + */ + + public byte monitorByte(byte expectedValue, String label) { + return this.monitorByte(expectedValue, label, false); + } + + public byte monitorByte(byte expectedValue, String label, boolean peek) { + // if (x != expectedValue) { +// Logger.info("MonitorTrip: " + label + ". Expected: " +// + expectedValue + " Got: " + x); +// } + return this.get(); + } + + public short monitorShort(short expectedValue, String label) { + return this.monitorShort(expectedValue, label, false); + } + + public short monitorShort(short expectedValue, String label, boolean peek) { + // if (x != expectedValue) { +// Logger.info("MonitorTrip: " + label + ". Expected: " +// + expectedValue + " Got: " + x); +// } + return this.getShort(); + } + + public int monitorInt(int expectedValue, String label) { + return this.monitorInt(expectedValue, label, false); + } + + public int monitorInt(int expectedValue, String label, boolean peek) { + // if (x != expectedValue) { +// Logger.info("MonitorTrip: " + label + ". Expected: " +// + expectedValue + " Got: " + x); +// } + return this.getInt(); + } + + public long monitorLong(long expectedValue, String label) { + return this.monitorLong(expectedValue, label, false); + } + + public long monitorLong(long expectedValue, String label, boolean peek) { + // if (x != expectedValue) { +// Logger.info("MonitorTrip: " + label + ". Expected: " +// + expectedValue + " Got: " + x); +// } + return this.getLong(); + } + + /* + * ByteBuffer delegates + */ + + /** + * @return + * @see java.nio.Buffer#hasRemaining() + */ + public final boolean hasRemaining() { + return bb.hasRemaining(); + } + + /** + * @return + * @see java.nio.Buffer#limit() + */ + public final int limit() { + return bb.limit(); + } + + /** + * @return + * @see java.nio.Buffer#position() + */ + public final int position() { + return bb.position(); + } + + public final Buffer position(int newPosition) { + return bb.position(newPosition); + } + + /** + * @return + * @see java.nio.Buffer#remaining() + */ + public final int remaining() { + return bb.remaining(); + } + + /* + * Status getters + */ + + protected ByteBuffer getBb() { + return bb; + } + + protected boolean isEndianFlip() { + return endianFlip; + } + + public String getByteArray() { + String ret = ""; + if (this.bb == null) + return ret; + byte[] bbyte = bb.array(); + if (bbyte == null) + return ret; + for (int i = 0; i < bbyte.length; i++) { + ret += Integer.toString((bbyte[i] & 0xff) + 0x100, 16).substring(1).toUpperCase(); + } + return ret; + } + +} diff --git a/src/engine/net/ByteBufferWriter.java b/src/engine/net/ByteBufferWriter.java new file mode 100644 index 00000000..bf55460b --- /dev/null +++ b/src/engine/net/ByteBufferWriter.java @@ -0,0 +1,206 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.util.ByteBufferUtils; +import org.joda.time.DateTime; + +import java.nio.ByteBuffer; +import java.time.LocalDateTime; +import java.util.UUID; + +//TODO possibly pool this class? Maybe make them static? +//TODO need to extract the SB specific stuff from here into subclass. + +public class ByteBufferWriter { + + private final ByteBuffer bb; + + public ByteBufferWriter(ByteBuffer bb) { + super(); + this.bb = bb; + } + + /* + * Putters + */ + + public synchronized void put(byte x) { + + this.bb.put(x); + } + + public synchronized void putChar(char x) { + + this.bb.putChar(x); + } + + public synchronized void putShort(short x) { + + this.bb.putShort(x); + } + + public synchronized void putInt(int x) { + this.bb.putInt(x); + } + + public synchronized void putLong(long x) { + this.bb.putLong(x); + } + + public synchronized void putDateTime(DateTime dateTime) { + this.put((byte) dateTime.getDayOfMonth()); + this.put((byte) ((byte) dateTime.getMonthOfYear() - 1)); + this.putInt(dateTime.getYear() - 1900); + this.put((byte) dateTime.getHourOfDay()); + this.put((byte) dateTime.getMinuteOfHour()); + this.put((byte) dateTime.getSecondOfDay()); + } + + public synchronized void putLocalDateTime(LocalDateTime dateTime) { + this.put((byte) dateTime.getDayOfMonth()); + this.put((byte) ((byte) dateTime.getMonth().getValue() - 1)); + this.putInt(dateTime.getYear() - 1900); + this.put((byte) dateTime.getHour()); + this.put((byte) dateTime.getMinute()); + this.put((byte) dateTime.getSecond()); + } + + public synchronized void putFloat(float x) { + this.bb.putFloat(x); + } + + public synchronized void putDouble(double x) { + this.bb.putDouble(x); + } + + public synchronized void putString(String x) { + ByteBufferUtils.putString(this.bb, x, false, false); + } + + public synchronized void putSmallString(String x) { + ByteBufferUtils.putString(this.bb, x, false, true); + } + + public synchronized void putHexString(String x) { + ByteBufferUtils.putHexString(this.bb, x, false); + } + + public synchronized void putHexStringWithoutSize(String data) { + int length = data.length() / 2; + + for (int i = 0; i < length; i++) { + bb.put((byte) Integer.parseInt(data.substring(2 * i, 2 * i + 2), 16)); + } + } + + public synchronized void putUnicodeString(String x) { + ByteBufferUtils.putUnicodeString(this.bb, x, false); + } + + public synchronized void putWriter(ByteBufferWriter writer) { + synchronized (writer) { + ByteBuffer bbin = writer.bb; + bbin.flip(); + this.bb.put(bbin); + } + } + + public synchronized void putBB(ByteBuffer bb) { + synchronized (bb) { + bb.flip(); + this.bb.put(bb); + } + } + + public synchronized void putIntAt(int value, int position) { + // mark end position + int endPosition = this.position(); + + // go back to the desired position + this.bb.position(position); + + // Write in the value: + this.putInt(value); + + // now go back to end: + this.bb.position(endPosition); + } + + public synchronized void putVector3f(Vector3f x) { + this.bb.putFloat(x.x); + this.bb.putFloat(x.y); + this.bb.putFloat(x.z); + } + + public synchronized void putVector3f(Vector3fImmutable x) { + this.bb.putFloat(x.x); + this.bb.putFloat(x.y); + this.bb.putFloat(x.z); + } + + public synchronized void putUUID(final UUID uuid) { + final long msb = uuid.getMostSignificantBits(); + final long lsb = uuid.getLeastSignificantBits(); + final byte[] buffer = new byte[16]; + + for (int i = 0; i < 8; i++) { + buffer[i] = (byte) (msb >>> 8 * (7 - i)); + } + + for (int i = 8; i < 16; i++) { + buffer[i] = (byte) (lsb >>> 8 * (7 - i)); + } + + this.bb.put(buffer); + } + + /** + * @return + * @see java.nio.Buffer#hasRemaining() + */ + public final boolean hasRemaining() { + return bb.hasRemaining(); + } + + /** + * @return + * @see java.nio.Buffer#limit() + */ + public final int limit() { + return bb.limit(); + } + + /** + * @return + * @see java.nio.Buffer#position() + */ + public final int position() { + return bb.position(); + } + + /** + * @return + * @see java.nio.Buffer#remaining() + */ + public final int remaining() { + return bb.remaining(); + } + + /* + * Getters + */ + + public synchronized ByteBuffer getBb() { + return this.bb; + } +} diff --git a/src/engine/net/CheckNetMsgFactoryJob.java b/src/engine/net/CheckNetMsgFactoryJob.java new file mode 100644 index 00000000..5f93b596 --- /dev/null +++ b/src/engine/net/CheckNetMsgFactoryJob.java @@ -0,0 +1,53 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.net; + +import engine.gameManager.ConfigManager; +import engine.job.AbstractJob; +import engine.net.client.msg.ClientNetMsg; +import org.pmw.tinylog.Logger; + +public class CheckNetMsgFactoryJob extends AbstractJob { + + private final AbstractConnection conn; + + public CheckNetMsgFactoryJob(AbstractConnection conn) { + super(); + this.conn = conn; + } + + @Override + protected void doJob() { + NetMsgFactory factory = conn.getFactory(); + + // Make any/all msg possible + factory.parseBuffer(); + + // get and route. + AbstractNetMsg msg = factory.getMsg(); + while (msg != null) { + + // Conditionally check to see if origin is set. + if (msg.getOrigin() == null) { + Logger.warn(msg.getClass().getSimpleName() + " had a NULL for its 'origin'."); + msg.setOrigin(this.conn); + } + + if (msg instanceof engine.net.client.msg.ClientNetMsg) { + ConfigManager.handler.handleClientMsg((ClientNetMsg) msg); + + } else { + Logger.error("Unrouteable message of type '" + msg.getClass().getSimpleName() + '\''); + } + + msg = factory.getMsg(); + } + } + +} diff --git a/src/engine/net/ConnectionMonitorJob.java b/src/engine/net/ConnectionMonitorJob.java new file mode 100644 index 00000000..06668bc6 --- /dev/null +++ b/src/engine/net/ConnectionMonitorJob.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.job.AbstractJob; +import engine.job.JobScheduler; +import engine.server.MBServerStatics; + +public class ConnectionMonitorJob extends AbstractJob { + + private final AbstractConnectionManager connMan; + private byte cnt; + + public ConnectionMonitorJob(AbstractConnectionManager connMan, byte cnt) { + super(); + this.connMan = connMan; + this.cnt = cnt; + this.cnt++; + } + + @Override + protected void doJob() { + + if (this.cnt >= 5) { + this.connMan.auditSocketChannelToConnectionMap(); + this.cnt = 0; + } else + this.connMan.auditSocketChannelToConnectionMap(); + + // Self Sustain + ConnectionMonitorJob cmj = new ConnectionMonitorJob(this.connMan, cnt); + JobScheduler.getInstance().scheduleJob(cmj, MBServerStatics.TIMEOUT_CHECKS_TIMER_MS); + + this.setCompletionStatus(JobCompletionStatus.SUCCESS); + } + +} diff --git a/src/engine/net/Dispatch.java b/src/engine/net/Dispatch.java new file mode 100644 index 00000000..d10e0068 --- /dev/null +++ b/src/engine/net/Dispatch.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.objects.PlayerCharacter; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import static engine.net.MessageDispatcher.itemPoolSize; + +/** + * Data class holds a message and a distribution list + */ + +public class Dispatch { + + private static final ConcurrentLinkedQueue dispatchPool = new ConcurrentLinkedQueue<>(); + + public PlayerCharacter player; + public AbstractNetMsg msg; + + public Dispatch(PlayerCharacter player, AbstractNetMsg msg) { + this.player = player; + this.msg = msg; + } + + public static Dispatch borrow(PlayerCharacter player, AbstractNetMsg msg) { + + Dispatch dispatch; + + dispatch = dispatchPool.poll(); + + if (dispatch == null) { + dispatch = new Dispatch(player, msg); + } else { + dispatch.player = player; + dispatch.msg = msg; + itemPoolSize.decrement(); + } + + return dispatch; + } + + public void reset() { + this.player = null; + this.msg = null; + } + + public void release() { + this.reset(); + dispatchPool.add(this); + itemPoolSize.increment(); + } +} diff --git a/src/engine/net/DispatchMessage.java b/src/engine/net/DispatchMessage.java new file mode 100644 index 00000000..65fcc87f --- /dev/null +++ b/src/engine/net/DispatchMessage.java @@ -0,0 +1,312 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.Enum.DispatchChannel; +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.gameManager.SessionManager; +import engine.math.Vector3fImmutable; +import engine.net.client.ClientConnection; +import engine.objects.AbstractWorldObject; +import engine.objects.Item; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.HashSet; + +import static engine.net.MessageDispatcher.dispatchCount; +import static engine.net.MessageDispatcher.maxRecipients; + +/* + * Dispatch Message is the main interface to Magicbane's threaded + * asynch message delivery system. + */ + +public class DispatchMessage { + + public static void startMessagePump() { + + Thread messageDispatcher; + messageDispatcher = new Thread(new MessageDispatcher()); + + messageDispatcher.setName("MessageDispatcher"); + messageDispatcher.start(); + } + + + public static void sendToAllInRange(AbstractWorldObject obj, + AbstractNetMsg msg) { + + if (obj == null) + return; + + if (obj.getObjectType() == GameObjectType.PlayerCharacter || obj.getObjectType() == GameObjectType.Mob || obj.getObjectType() == GameObjectType.NPC || obj.getObjectType() == GameObjectType.Corpse) + dispatchMsgToInterestArea(obj, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + else + dispatchMsgToInterestArea(obj, msg, DispatchChannel.PRIMARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false); + + } + + // Dispatches a message to a playercharacter's interest area + // Method includes handling of exclusion rules for visibility, self, etc. + + public static void dispatchMsgToInterestArea(AbstractWorldObject sourceObject, AbstractNetMsg msg, DispatchChannel dispatchChannel, int interestRange, boolean sendToSelf, boolean useIgnore) { + + Dispatch messageDispatch; + HashSet gridList; + PlayerCharacter gridPlayer; + AbstractWorldObject dispatchSource; + PlayerCharacter sourcePlayer = null; + long recipientCount = 0; + + if (sourceObject == null) + return; + + // If the source of the message is a structure, item or player + // setup our method variables accordingly. + + switch (sourceObject.getObjectType()) { + case Item: + dispatchSource = (AbstractWorldObject) ((Item) sourceObject).getOwner(); + break; + case PlayerCharacter: + dispatchSource = sourceObject; + sourcePlayer = (PlayerCharacter) sourceObject; + if (sourcePlayer.getClientConnection() != null && sendToSelf) { + Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + + break; + default: + dispatchSource = sourceObject; + } + + gridList = WorldGrid.getObjectsInRangePartial(dispatchSource.getLoc(), interestRange, MBServerStatics.MASK_PLAYER); + + for (AbstractWorldObject gridObject : gridList) { + + gridPlayer = (PlayerCharacter) gridObject; + + // Apply filter options if source of dispatch is a player + + if ((dispatchSource.getObjectType() == GameObjectType.PlayerCharacter) && + (sourcePlayer != null)) { + + if (gridPlayer.getObjectUUID() == sourcePlayer.getObjectUUID()) + continue; + + if ((useIgnore == true) && (gridPlayer.isIgnoringPlayer(sourcePlayer) == true)) + continue; + + if (gridPlayer.canSee(sourcePlayer) == false) + continue; + } + + messageDispatch = Dispatch.borrow(gridPlayer, msg); + MessageDispatcher.send(messageDispatch, dispatchChannel); + recipientCount++; + } + + // Update metrics + + if (recipientCount > maxRecipients[dispatchChannel.getChannelID()]) + maxRecipients[dispatchChannel.getChannelID()] = recipientCount; + + dispatchCount[dispatchChannel.getChannelID()].increment(); + } + + public static void dispatchMsgToInterestArea(Vector3fImmutable targetLoc, AbstractWorldObject sourceObject, AbstractNetMsg msg, DispatchChannel dispatchChannel, int interestRange, boolean sendToSelf, boolean useIgnore) { + + Dispatch messageDispatch; + HashSet gridList; + PlayerCharacter gridPlayer; + AbstractWorldObject dispatchSource; + PlayerCharacter sourcePlayer = null; + long recipientCount = 0; + + if (sourceObject == null) + return; + + // If the source of the message is a structure, item or player + // setup our method variables accordingly. + + switch (sourceObject.getObjectType()) { + case Item: + dispatchSource = (AbstractWorldObject) ((Item) sourceObject).getOwner(); + break; + case PlayerCharacter: + dispatchSource = sourceObject; + sourcePlayer = (PlayerCharacter) sourceObject; + + if (sourcePlayer.getClientConnection() != null && sendToSelf) { + Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + + break; + default: + dispatchSource = sourceObject; + } + + gridList = WorldGrid.getObjectsInRangePartial(targetLoc, interestRange, MBServerStatics.MASK_PLAYER); + + for (AbstractWorldObject gridObject : gridList) { + + gridPlayer = (PlayerCharacter) gridObject; + + // Apply filter options if source of dispatch is a player + + if ((dispatchSource.getObjectType() == GameObjectType.PlayerCharacter) && + (sourcePlayer != null)) { + + if (gridPlayer.getObjectUUID() == sourcePlayer.getObjectUUID()) + continue; + + if ((useIgnore == true) && (gridPlayer.isIgnoringPlayer(sourcePlayer) == true)) + continue; + + if (gridPlayer.canSee(sourcePlayer) == false) + continue; + } + + messageDispatch = Dispatch.borrow(gridPlayer, msg); + MessageDispatcher.send(messageDispatch, dispatchChannel); + recipientCount++; + } + + // Update metrics + + if (recipientCount > maxRecipients[dispatchChannel.getChannelID()]) + maxRecipients[dispatchChannel.getChannelID()] = recipientCount; + + dispatchCount[dispatchChannel.getChannelID()].increment(); + } + + // Sends a message to all players in the game + + public static void dispatchMsgToAll(AbstractNetMsg msg) { + + Dispatch messageDispatch; + long recipientCount = 0; + + // Send message to nobody? No thanks! + + if (SessionManager.getAllActivePlayerCharacters().isEmpty()) + return; + + // Messages to all we will default to the secondary dispatch + // delivery channel. They are generally large, or inconsequential. + + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) { + messageDispatch = Dispatch.borrow(player, msg); + MessageDispatcher.send(messageDispatch, DispatchChannel.SECONDARY); + recipientCount++; + } + + // Update metrics + + if (recipientCount > maxRecipients[DispatchChannel.SECONDARY.getChannelID()]) + maxRecipients[DispatchChannel.SECONDARY.getChannelID()] = recipientCount; + + dispatchCount[DispatchChannel.SECONDARY.getChannelID()].increment(); + + } + + public static void dispatchMsgToAll(PlayerCharacter source, AbstractNetMsg msg, boolean ignore) { + + Dispatch messageDispatch; + long recipientCount = 0; + + // Send message to nobody? No thanks! + + if (SessionManager.getAllActivePlayerCharacters().isEmpty()) + return; + + // Messages to all we will default to the secondary dispatch + // delivery channel. They are generally large, or inconsequential. + + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) { + + if (ignore && player.isIgnoringPlayer(source)) + continue; + + messageDispatch = Dispatch.borrow(player, msg); + MessageDispatcher.send(messageDispatch, DispatchChannel.SECONDARY); + recipientCount++; + } + + // Update metrics + + if (recipientCount > maxRecipients[DispatchChannel.SECONDARY.getChannelID()]) + maxRecipients[DispatchChannel.SECONDARY.getChannelID()] = recipientCount; + + dispatchCount[DispatchChannel.SECONDARY.getChannelID()].increment(); + + } + + // Sends a message to an arbitrary distribution list + + public static void dispatchMsgDispatch(Dispatch messageDispatch, DispatchChannel dispatchChannel) { + + if (messageDispatch == null) { + Logger.info("DISPATCH Null for DispatchMessage!"); + return; + } + + // No need to serialize an empty list + + if (messageDispatch.player == null) { + Logger.info("Player Null for Dispatch!"); + messageDispatch.release(); + return; + } + + + MessageDispatcher.send(messageDispatch, dispatchChannel); + + dispatchCount[dispatchChannel.getChannelID()].increment(); + + } + + protected static void serializeDispatch(Dispatch messageDispatch) { + ClientConnection connection; + + if (messageDispatch.player == null) { + Logger.info("Player null in serializeDispatch"); + messageDispatch.release(); + return; + } + + connection = messageDispatch.player.getClientConnection(); + + if ((connection == null) || (connection.isConnected() == false)) { + messageDispatch.release(); + return; + } + + if (messageDispatch.msg == null) { + Logger.error("null message sent to " + messageDispatch.player.getName()); + messageDispatch.release(); + return; + } + + if (!connection.sendMsg(messageDispatch.msg)) + Logger.error(messageDispatch.msg.getProtocolMsg() + " failed sending to " + messageDispatch.player.getName()); + + messageDispatch.release(); + } + + +} diff --git a/src/engine/net/ItemProductionManager.java b/src/engine/net/ItemProductionManager.java new file mode 100644 index 00000000..78577668 --- /dev/null +++ b/src/engine/net/ItemProductionManager.java @@ -0,0 +1,135 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.Enum.DispatchChannel; +import engine.objects.ProducedItem; +import org.pmw.tinylog.Logger; + +import java.util.HashSet; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.atomic.LongAdder; + +/** + * Thread blocks until MagicBane dispatch messages are + * enqueued then processes them in FIFO order. The collection + * is thread safe. + *

+ * Any large messages not time sensitive such as load object + * sent to more than a single individual should be spawned + * individually on a DispatchMessageThread. + */ + + +public enum ItemProductionManager implements Runnable { + + ITEMPRODUCTIONMANAGER; + + + // Instance variables + + @SuppressWarnings("unchecked") // Cannot have arrays of generics in java. + private static final DelayQueue producedQueue = new DelayQueue<>(); + public static volatile long[] messageCount = new long[DispatchChannel.values().length]; + + // Class variables + public static LongAdder[] dispatchCount = new LongAdder[DispatchChannel.values().length]; + + + // Performance metrics + public static volatile long[] maxRecipients = new long[DispatchChannel.values().length]; + public static LongAdder dispatchPoolSize = new LongAdder(); + public static HashSet FailedItems = new HashSet<>(); + public Thread itemProductionThread = null; + private ItemQueue itemQueue; + private long nextFailedItemAudit; + + // Thread constructor + + public static void send(ItemQueue item) { + + // Don't queue up empty dispatches! + + if (item == null) + return; + + producedQueue.add(item); + + } + + public static String getNetstatString() { + + String outString = null; + String newLine = System.getProperty("line.separator"); + outString = "[LUA_NETSTA()]" + newLine; + outString += "poolSize: " + dispatchPoolSize.longValue() + '\n'; + + for (DispatchChannel dispatchChannel : DispatchChannel.values()) { + + outString += "Channel: " + dispatchChannel.name() + '\n'; + outString += "Dispatches: " + dispatchCount[dispatchChannel.getChannelID()].longValue() + '\n'; + outString += "Messages: " + messageCount[dispatchChannel.getChannelID()] + '\n'; + outString += "maxRecipients: " + maxRecipients[dispatchChannel.getChannelID()] + '\n'; + } + return outString; + } + + public void startMessagePump() { + + itemProductionThread = new Thread(this); + itemProductionThread.setName("ItemProductionManager"); + + } + + public void initialize() { + itemProductionThread.start(); + } + + @Override + public void run() { + + + while (true) { + try { + + this.itemQueue = producedQueue.take(); + + if (this.itemQueue == null) { + return; + } + + if (this.itemQueue != null) { + if (this.itemQueue.item == null) { + this.itemQueue.release(); + return; + } + + + boolean created = this.itemQueue.item.finishProduction(); + + if (!created) + FailedItems.add(this.itemQueue.item); + + this.itemQueue.release(); + + + } + + + } catch (Exception e) { + Logger.error(e); + } + + } + } + + // For Debugging: + //Logger.error("MessageDispatcher", messageDispatch.msg.getOpcodeAsString() + " sent to " + messageDispatch.playerList.size() + " players"); +} diff --git a/src/engine/net/ItemQueue.java b/src/engine/net/ItemQueue.java new file mode 100644 index 00000000..e5373324 --- /dev/null +++ b/src/engine/net/ItemQueue.java @@ -0,0 +1,86 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.objects.ProducedItem; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +import static engine.net.MessageDispatcher.itemPoolSize; + +/** + * Data class holds a message and a distribution list + */ + +public class ItemQueue implements Delayed { + + private static final ConcurrentLinkedQueue itemPool = new ConcurrentLinkedQueue<>(); + + public ProducedItem item; + public long delayTime; + + + public ItemQueue(ProducedItem item, long delayTime) { + this.item = item; + this.delayTime = System.currentTimeMillis() + delayTime; + + } + + public static ItemQueue borrow(ProducedItem item, long delayTime) { + + ItemQueue itemQueue; + + itemQueue = itemPool.poll(); + + if (itemQueue == null) { + itemQueue = new ItemQueue(item, delayTime); + } else { + itemQueue.item = item; + itemQueue.delayTime = System.currentTimeMillis() + delayTime; + itemPoolSize.decrement(); + } + + return itemQueue; + } + + public void reset() { + this.item = null; + this.delayTime = 0; + } + + public void release() { + this.reset(); + itemPool.add(this); + itemPoolSize.increment(); + } + + @Override + public int compareTo(Delayed another) { + ItemQueue anotherTask = (ItemQueue) another; + + if (this.delayTime < anotherTask.delayTime) { + return -1; + } + + if (this.delayTime > anotherTask.delayTime) { + return 1; + } + + return 0; + } + + @Override + public long getDelay(TimeUnit unit) { + long difference = delayTime - System.currentTimeMillis(); + return unit.convert(difference, TimeUnit.MILLISECONDS); + } +} diff --git a/src/engine/net/MessageDispatcher.java b/src/engine/net/MessageDispatcher.java new file mode 100644 index 00000000..862be7b7 --- /dev/null +++ b/src/engine/net/MessageDispatcher.java @@ -0,0 +1,130 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.Enum.DispatchChannel; +import org.pmw.tinylog.Logger; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.LongAdder; +import java.util.regex.Pattern; + +/** + * Thread blocks until MagicBane dispatch messages are + * enqueued then processes them in FIFO order. The collection + * is thread safe. + *

+ * Any large messages not time sensitive such as load object + * sent to more than a single individual should be spawned + * individually on a DispatchMessageThread. + */ + +public class MessageDispatcher implements Runnable { + + // Instance variables + + @SuppressWarnings("unchecked") // Cannot have arrays of generics in java. + private static final ConcurrentLinkedQueue[] _messageQueue = new ConcurrentLinkedQueue[DispatchChannel.values().length]; + private static final LinkedBlockingQueue _blockingQueue = new LinkedBlockingQueue<>(); + + // Class variables + public static volatile long[] messageCount = new long[DispatchChannel.values().length]; + public static LongAdder[] dispatchCount = new LongAdder[DispatchChannel.values().length]; + + // Performance metrics + public static volatile long[] maxRecipients = new long[DispatchChannel.values().length]; + public static LongAdder itemPoolSize = new LongAdder(); + private final Pattern filterPattern; // Unused, but just in case + private Dispatch messageDispatch; + + // Thread constructor + + public MessageDispatcher() { + + // Create new FIFO queues for this network thread + + for (DispatchChannel dispatchChannel : DispatchChannel.values()) { + _messageQueue[dispatchChannel.getChannelID()] = new ConcurrentLinkedQueue<>(); + dispatchCount[dispatchChannel.getChannelID()] = new LongAdder(); + } + + filterPattern = Pattern.compile("[^\\p{ASCII}]"); + Logger.info(" Dispatcher thread has started!"); + + } + + public static void send(Dispatch messageDispatch, DispatchChannel dispatchChannel) { + + // Don't queue up empty dispatches! + + if (messageDispatch.player == null) + return; + + _messageQueue[dispatchChannel.getChannelID()].add(messageDispatch); + _blockingQueue.add(true); + + // Update performance metrics + + messageCount[dispatchChannel.getChannelID()]++; + + } + + public static String getNetstatString() { + + String outString = null; + String newLine = System.getProperty("line.separator"); + outString = "[LUA_NETSTA()]" + newLine; + outString += "poolSize: " + itemPoolSize.longValue() + '\n'; + + for (DispatchChannel dispatchChannel : DispatchChannel.values()) { + + outString += "Channel: " + dispatchChannel.name() + '\n'; + outString += "Dispatches: " + dispatchCount[dispatchChannel.getChannelID()].longValue() + '\n'; + outString += "Messages: " + messageCount[dispatchChannel.getChannelID()] + '\n'; + outString += "maxRecipients: " + maxRecipients[dispatchChannel.getChannelID()] + '\n'; + } + return outString; + } + + @Override + public void run() { + + boolean shouldBlock; + + while (true) { + try { + + shouldBlock = true; + + for (DispatchChannel dispatchChannel : DispatchChannel.values()) { + + this.messageDispatch = _messageQueue[dispatchChannel.getChannelID()].poll(); + + if (this.messageDispatch != null) { + DispatchMessage.serializeDispatch(this.messageDispatch); + shouldBlock = false; + } + + } + + if (shouldBlock == true) + shouldBlock = _blockingQueue.take(); + + } catch (Exception e) { + Logger.error(e); + } + + } + } + + // For Debugging: + //Logger.error("MessageDispatcher", messageDispatch.msg.getOpcodeAsString() + " sent to " + messageDispatch.playerList.size() + " players"); +} diff --git a/src/engine/net/NetMsgFactory.java b/src/engine/net/NetMsgFactory.java new file mode 100644 index 00000000..c1cbba91 --- /dev/null +++ b/src/engine/net/NetMsgFactory.java @@ -0,0 +1,417 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.exception.FactoryBuildException; +import engine.gameManager.ChatManager; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; + +public class NetMsgFactory { + + // NetMsg Opcode to Constructor List + private static final HashMap netMsgDefinitions = new HashMap<>(); + private static final int FLOOD_CONTROL_TRIP_SETPOINT = 1000; + // Standardize the error strings + private static String ALL_GOOD_JUST_NOT_ENOUGH_BYTES = "Not enough Bytes"; + private static String DESERIALIZATION_FAILURE = "Deserialization Failure"; + private static String UNIMPLEMENTED_OPCODE = "Unimplemented Opcode"; + private static String UNKNOWN_OPCODE = "Unknown Opcode"; + private final ArrayList msgOutbox; + private final AbstractConnection owner; + protected ByteBuffer internalBuffer; + private boolean enableFloodControl; + private boolean bypassFloodControl; // temp bypass + private boolean floodControlTripped; + private int badOpcodeCount; + private int lastMsgPosition = 0; + + public NetMsgFactory(AbstractConnection origin, boolean enableFloodControl) { + this.internalBuffer = Network.byteBufferPool.getBuffer(18); //256k buffer + + this.bypassFloodControl = false; + this.msgOutbox = new ArrayList<>(); + this.enableFloodControl = enableFloodControl; + this.floodControlTripped = false; + this.owner = origin; + } + + public NetMsgFactory(AbstractConnection origin) { + this(origin, true); + } + + public static String getByteArray(ByteBufferReader reader) { + String ret = ""; + if (reader == null) + return ret; + + ByteBuffer bb = reader.getBb(); + if (bb == null) + return ret; + + int length = bb.limit(); // - bb.position(); + ByteBuffer temp = bb.duplicate(); + temp.position(bb.limit()); + temp.flip(); + for (int i = 0; i < length; i++) { + ret += Integer.toString((temp.get() & 0xff) + 0x100, 16).substring(1).toUpperCase(); + } + return ret; + } + + @SuppressWarnings("unchecked") + private static AbstractNetMsg getNewInstanceOf(int opcode, + AbstractConnection origin, ByteBufferReader reader) { + try { + + Protocol protocolMsg = Protocol.getByOpcode(opcode); + + if (protocolMsg == Protocol.NONE) { + + String errorString = DateTime.now().toString() + origin.lastProtocol.name(); + + int errorCode = errorString.hashCode(); + + + if (origin instanceof ClientConnection) { + PlayerCharacter player = ((ClientConnection) origin).getPlayerCharacter(); + if (player != null) { +// if (MBServerStatics.worldServerName.equals("Grief")) + Logger.error("Invalid protocol msg for player " + player.getFirstName() + " : " + opcode + " lastopcode: " + origin.lastProtocol.name() + " Error Code : " + errorCode); + } else + Logger.error("Invalid protocol msg : " + opcode + " lastopcode: " + origin.lastProtocol.name() + " Error Code : " + errorCode); + + } + + return null; + } + origin.lastProtocol = protocolMsg; + + if (protocolMsg.constructor == null) { + return null; + } + + + Constructor constructor = protocolMsg.constructor; + + if (constructor == null) + return null; + + Object[] myArgs = new Object[2]; + myArgs[0] = origin; + myArgs[1] = reader; + + Object object = constructor.newInstance(myArgs); + + if (object instanceof engine.net.AbstractNetMsg) + return (AbstractNetMsg) object; + + } catch (IllegalAccessException | IllegalArgumentException | InstantiationException | + ExceptionInInitializerError e) { + Logger.error(e); + + } catch (InvocationTargetException e) { + if (e.getCause() != null + && e.getCause().getClass() == BufferUnderflowException.class) + throw new BufferUnderflowException(); + Logger.error(e); + } + return null; + } + + public final void addData(byte[] ba) { + // Dont use prefab BB's here, since sizeof(ba) is unknown. + ByteBuffer bb = ByteBuffer.wrap(ba); + bb.position(bb.capacity()); + this.addData(bb); + } + + public final void addData(ByteBuffer newData) { + synchronized (this.internalBuffer) { + + int newCapacity = this.internalBuffer.position() + newData.position(); + + if (newCapacity >= this.internalBuffer.capacity()) { + //Resize!!!! + Logger.warn( + "Bytebuffer is being be Resized."); + + //Get a newer, bigger BB + ByteBuffer newBB = Network.byteBufferPool.getBufferToFit((int) (newCapacity * 1.5)); + + //Copy old data in + this.internalBuffer.flip(); + newBB.put(this.internalBuffer); + + //Get a handle on old BB + ByteBuffer oldBB = this.internalBuffer; + + //install new BB + this.internalBuffer = newBB; + + //Return old BB + Network.byteBufferPool.putBuffer(oldBB); + } + + synchronized (newData) { + // Copy over the data. + newData.flip(); + + try { + this.internalBuffer.put(newData); + } catch (Exception e) { + Logger.error(e.toString()); + // TODO figure out how to handle this error. + } + } + } + } + + public void parseBuffer() { + // Check flood control first + if (this.floodControlTripped) + // this.conn.disconnect(); + return; + + // MBServer.jobMan.submitJob(new ParseBufferJob(this)); + this._parseBuffer(); + } + + /** + * This function makes a copy of the current internal byte buffer and loads + * the copy into a ByteBufferReader. It is copied so that the Factory can + * continue to accumulate data on the internal buffer from the + * socketChannels. The ByteBufferReader is then used in an attempt to build + * an AbstractNetMsg subclass based on protocolMsg. If a message is successfully + * built, the bytes used are removed from the Factory's internal byte + * buffer. + * + * @return + * @throws Exception + */ + protected void _parseBuffer() { + synchronized (this.internalBuffer) { + while (this.internalBuffer.position() > 0) { + // Check flood control first + if (this.floodControlTripped) + break; + + ByteBufferReader reader = null; + + // Check to see if the minimum amount of data is here: + if (this.internalBuffer.position() < 4) + // nothing wrong, just not enough info yet. + break; + + // copy internal buffer into a reader + reader = new ByteBufferReader(this.internalBuffer, false); + + // Reset the limit to the capacity + this.internalBuffer.limit(this.internalBuffer.capacity()); + + try { + AbstractNetMsg msg = this.tryBuild(owner, reader); + + // error, null messages are being returned on unhandled + // opcodes + // for some reason + if (msg == null) + throw new FactoryBuildException(UNIMPLEMENTED_OPCODE); + + + if (owner.getClass().equals(ClientConnection.class)) { + ClientConnection client = (ClientConnection) owner; + client.setLastOpcode(msg.getProtocolMsg().opcode); + } + + + // Logger.debug("Adding a " + msg.getSimpleClassName() + // + " to the outbox."); + this.addMsgToOutBox(msg); + + this.dropLeadingBytesFromBuffer(reader.position()); + this.bypassFloodControl = false; + + } catch (FactoryBuildException e) { + String error = e.getMessage(); + int readerPos = reader.position(); + + if (error.equals(ALL_GOOD_JUST_NOT_ENOUGH_BYTES)) { + break; + } + // no worries, just break. + + else if (error.equals(DESERIALIZATION_FAILURE)) { + // Lop readerPos bytes off the buffer. + this.dropLeadingBytesFromBuffer(readerPos); + + // Lets bypass flood control for now. + this.bypassFloodControl = true; + continue; + + } else if (error.equals(UNIMPLEMENTED_OPCODE)) { + + if (owner.lastProtocol != null && owner.lastProtocol.constructor == null) { + this.dropLeadingBytesFromBuffer(readerPos); + this.bypassFloodControl = true; + continue; + } + + // Lop readerPos bytes off the buffer. + if (reader.position() >= 4) + reader.position(reader.position() - 4); + int newPosition = Protocol.FindNextValidOpcode(reader); + this.dropLeadingBytesFromBuffer(newPosition); + // Lets bypass flood control for now. + this.bypassFloodControl = true; + + continue; + + } else if (error.equals(UNKNOWN_OPCODE)) { + + if (owner.lastProtocol != null && owner.lastProtocol.constructor == null) { + this.dropLeadingBytesFromBuffer(readerPos); + this.bypassFloodControl = true; + continue; + } + // We don't know what this is or how long, so dump the + // first + // byte and try again + if (reader.position() >= 4) + reader.position(reader.position() - 4); + int newPosition = Protocol.FindNextValidOpcode(reader); + this.dropLeadingBytesFromBuffer(newPosition); + // Lets bypass flood control for now. + this.bypassFloodControl = true; + + continue; + } + } catch (Exception e) { + // TODO FIX THIS!!!! +// Logger.error( e); + + }// end catch + + } // end while loop + } + }// end fn + + public AbstractNetMsg tryBuild(AbstractConnection origin, + ByteBufferReader reader) throws FactoryBuildException { + try { + + // Get the protocolMsg + int opcode = reader.getInt(); + // String ocHex = StringUtils.toHexString(protocolMsg); + + if (MBServerStatics.PRINT_INCOMING_OPCODES) + try { + Logger.info("Incoming protocolMsg: " + + Protocol.getByOpcode(opcode).name() + " " + Integer.toHexString(opcode) + ", size: " + reader.getBb().limit() + "; " + getByteArray(reader)); + } catch (Exception e) { + Logger.error(e); + } + + return NetMsgFactory.getNewInstanceOf(opcode, origin, reader); + + } catch (BufferUnderflowException e) { + // This is okay. it indicates that we recognized the protocolMsg, but + // there isn't enough information in + // the reader to complete the NetMsg deserialization + throw new FactoryBuildException(ALL_GOOD_JUST_NOT_ENOUGH_BYTES); + + } + } + + private void incrBadOpcodeCount() { + // keeping this a nested if for Troubleshooting/clarity + if (this.enableFloodControl == true) + if (this.bypassFloodControl == false) { + ++this.badOpcodeCount; + + + if (this.badOpcodeCount >= FLOOD_CONTROL_TRIP_SETPOINT) { + if (this.owner != null) { + if (this.owner instanceof ClientConnection) { + ClientConnection client = (ClientConnection) this.owner; + if (client.getPlayerCharacter() != null) { + ChatManager.chatSystemError(client.getPlayerCharacter(), "TRIPPED Flood Control! PLEASE RELOG!"); + Logger.info(client.getPlayerCharacter().getName() + " Tripped Flood Control!" + this.badOpcodeCount); + } + + } + } + this.floodControlTripped = true; + } else { + if (this.owner != null) { + if (this.owner instanceof ClientConnection) { + ClientConnection client = (ClientConnection) this.owner; + if (client.getPlayerCharacter() != null) { + ChatManager.chatSystemError(client.getPlayerCharacter(), "Client sending bad messages. bad message Count " + this.badOpcodeCount); + Logger.info(client.getPlayerCharacter().getName() + " has been caught sending bad opcodes. Bad Opcode Count " + this.badOpcodeCount); + } + + } + + + } + } + + } + } + + protected final void dropLeadingBytesFromBuffer(int numberOfBytes) { + this.internalBuffer.limit(this.internalBuffer.position()); + this.internalBuffer.position(numberOfBytes); + this.internalBuffer.compact(); // Compact + } + + protected boolean addMsgToOutBox(AbstractNetMsg msg) { + synchronized (this.msgOutbox) { + return msgOutbox.add(msg); + } + } + + public AbstractNetMsg getMsg() { + synchronized (this.msgOutbox) { + if (this.msgOutbox.isEmpty()) + return null; + return msgOutbox.remove(0); + } + } + + public boolean hasMsg() { + synchronized (this.msgOutbox) { + return !msgOutbox.isEmpty(); + } + } + + public boolean hasData() { + synchronized (this.internalBuffer) { + return (this.internalBuffer.position() != 0); + } + } + + public ByteBuffer getInternalBuffer() { + return internalBuffer; + } + +} diff --git a/src/engine/net/NetMsgHandler.java b/src/engine/net/NetMsgHandler.java new file mode 100644 index 00000000..b208ffd3 --- /dev/null +++ b/src/engine/net/NetMsgHandler.java @@ -0,0 +1,17 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.net.client.msg.ClientNetMsg; + +public interface NetMsgHandler { + + public abstract boolean handleClientMsg(ClientNetMsg msg); +} diff --git a/src/engine/net/NetMsgStat.java b/src/engine/net/NetMsgStat.java new file mode 100644 index 00000000..eef4b0b2 --- /dev/null +++ b/src/engine/net/NetMsgStat.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.net.client.Protocol; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class NetMsgStat { + + private final Protocol protocolMsg; + private final AtomicLong total = new AtomicLong(); + private final AtomicLong count = new AtomicLong(); + private final AtomicInteger average = new AtomicInteger(); + private final AtomicInteger max = new AtomicInteger(); + private final AtomicInteger countUnderAverage = new AtomicInteger(); + private final AtomicInteger countOverAverage = new AtomicInteger(); + private final AtomicInteger countOverMax = new AtomicInteger(); + + public NetMsgStat(Protocol protocolMsg, int startSize) { + + if (startSize < 10) + startSize = 10; + + if (startSize > 30) + startSize = 30; + + this.protocolMsg = protocolMsg; + this.total.set(startSize); + this.count.set(1L); + this.average.set(10); + this.max.set(startSize); + this.countUnderAverage.set(0); + this.countOverAverage.set(0); + this.countOverMax.set(0); + } + + public void updateStat(int i) { + this.total.addAndGet(i); + this.count.incrementAndGet(); + + int avg = (int) (this.total.get() / this.count.get()); + if (avg < 0) + avg = 0; + else if (avg > 30) + avg = 30; + else + this.average.set(avg); + + if (this.max.get() < i) + this.max.set(i); + + if (i <= avg) + this.countUnderAverage.incrementAndGet(); + else if (i < this.max.get()) + this.countOverAverage.incrementAndGet(); + else + this.countOverMax.incrementAndGet(); + } + + public Protocol getOpcode() { + return this.protocolMsg; + } + + public int getMax() { + return this.max.get(); + } + +} diff --git a/src/engine/net/Network.java b/src/engine/net/Network.java new file mode 100644 index 00000000..28502505 --- /dev/null +++ b/src/engine/net/Network.java @@ -0,0 +1,60 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net; + +import engine.pooling.MultisizeByteBufferPool; + +import java.nio.ByteBuffer; + +public class Network { + + public static final int INITIAL_SOCKET_BUFFER_SIZE = 128 * 1024; + public static final int INITIAL_BYTEBUFFER_POOL_SIZE = 256; + + public static final MultisizeByteBufferPool byteBufferPool = new MultisizeByteBufferPool(); + + public static void init() { + //Force a few to be created. + + //Small (2^10-15) + for (int a = 10; a < 16; ++a) { + for (int i = 0; i < 50; ++i) { + byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[a])); + } + } + + //standard size (2^16) + for (int i = 0; i < 100; ++i) { + byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[16])); + } + + //Large (2^17) + for (int i = 0; i < 50; ++i) { + byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[17])); + } + + // NetMsgFactory size (2^18) + for (int i = 0; i < 64; ++i) { + byteBufferPool.putBuffer(ByteBuffer + .allocateDirect(MultisizeByteBufferPool.powersOfTwo[18])); + } + + //Very Large (2^19) + for (int i = 0; i < 25; ++i) { + byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[19])); + } + + //Very Large (2^20) + for (int i = 0; i < 10; ++i) { + byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[20])); + } + } + +} diff --git a/src/engine/net/client/ClientAuthenticator.java b/src/engine/net/client/ClientAuthenticator.java new file mode 100644 index 00000000..937ac747 --- /dev/null +++ b/src/engine/net/client/ClientAuthenticator.java @@ -0,0 +1,377 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client; + +import engine.net.AbstractConnection; +import engine.server.MBServerStatics; +import engine.util.ByteUtils; +import org.pmw.tinylog.Logger; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.ShortBufferException; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPublicKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.security.*; + +public class ClientAuthenticator { + + private static final byte P_Bytes[] = {(byte) 0xFB, (byte) 0x46, (byte) 0x56, (byte) 0xB4, (byte) 0xBE, (byte) 0x81, (byte) 0xA4, + (byte) 0x2C, (byte) 0x37, (byte) 0xC4, (byte) 0xA2, (byte) 0x61, (byte) 0x4A, (byte) 0xAC, (byte) 0x65, (byte) 0x90, + (byte) 0x31, (byte) 0xB6, (byte) 0x83, (byte) 0x26, (byte) 0x63, (byte) 0x94, (byte) 0x08, (byte) 0x95, (byte) 0x56, + (byte) 0x8D, (byte) 0x5E, (byte) 0xBF, (byte) 0x94, (byte) 0x10, (byte) 0x5A, (byte) 0x37, (byte) 0xB6, (byte) 0x82, + (byte) 0x1A, (byte) 0x75, (byte) 0x2B, (byte) 0xF1, (byte) 0x94, (byte) 0xB7, (byte) 0x7E, (byte) 0x56, (byte) 0xC6, + (byte) 0xD1, (byte) 0xF5, (byte) 0x18, (byte) 0xE1, (byte) 0xA5, (byte) 0x13, (byte) 0x9E, (byte) 0xC1, (byte) 0x85, + (byte) 0x98, (byte) 0xB7, (byte) 0x32, (byte) 0xDB, (byte) 0x38, (byte) 0x09, (byte) 0x1A, (byte) 0xF8, (byte) 0x5C, + (byte) 0xDA, (byte) 0x4F, (byte) 0x9F, (byte) 0x67, (byte) 0x93, (byte) 0x72, (byte) 0x8F, (byte) 0x75, (byte) 0x4F, + (byte) 0x0B, (byte) 0xBD, (byte) 0x69, (byte) 0x61, (byte) 0x97, (byte) 0x1F, (byte) 0xEE, (byte) 0xFB, (byte) 0x5B, + (byte) 0xB0, (byte) 0x85, (byte) 0xC4, (byte) 0x27, (byte) 0x7E, (byte) 0x41, (byte) 0x42, (byte) 0xC2, (byte) 0xF1, + (byte) 0xDA, (byte) 0x64, (byte) 0x8F, (byte) 0x4E, (byte) 0x28, (byte) 0xFD, (byte) 0x2A, (byte) 0x63}; + private static final BigInteger P = new BigInteger(1, P_Bytes); + private static final BigInteger G = BigInteger.valueOf(5); + private final AbstractConnection origin; + private ByteBuffer buffer = ByteBuffer.allocate(100); + private byte[] secretKeyBytes = new byte[16]; + private SecretKeySpec BFKey; + private Cipher cipher; + private byte[] iVecEnc = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + private byte[] iVecDec = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + private int iVecEncOffset = 0; + private int iVecDecOffset = 0; + private int totalRead = 0; + private KeyFactory keyFactory; + private DHParameterSpec dhParamSpec; + private KeyPairGenerator keyPairGen; + private KeyAgreement keyAgree; + private boolean initialized = false; + private boolean keyInit = false; + private byte[] secretKey; + private byte[] serverPublicKey; + + public ClientAuthenticator(AbstractConnection origin) { + super(); + this.origin = origin; + try { + // init the resuable Crypto Stuff. + this.keyFactory = KeyFactory.getInstance("DH"); + this.dhParamSpec = new DHParameterSpec(ClientAuthenticator.P, ClientAuthenticator.G); + + this.keyPairGen = KeyPairGenerator.getInstance("DH"); + this.keyPairGen.initialize(this.dhParamSpec); + this.keyAgree = KeyAgreement.getInstance("DH"); + + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + Logger.error("NoSuchAlgorithmException " + e.getMessage()); + this.keyInit = false; + return; + } + + this.keyInit = true; + } + + private void calcKeys(AbstractConnection origin, byte[] clientPublicKeyBytes) { + + try { + // get the forwarded client public key, in byte[] form. + + // Convert client public key to a BigInteger + BigInteger clientPublicKeyBI = new BigInteger(1, clientPublicKeyBytes); + + // convert the client's Public Key to a DHPublicKey object + DHPublicKeySpec dhKeySpec = new DHPublicKeySpec(clientPublicKeyBI, ClientAuthenticator.P, ClientAuthenticator.G); + DHPublicKey clientPublicKey = (DHPublicKey) this.keyFactory.generatePublic(dhKeySpec); + + // Now calculate the server's PublicKey + byte[] serverPublicKeyBytes = new byte[96]; + boolean invalid = true; + + int tryCnt = 1; + while (invalid) { + KeyPair keyPair = keyPairGen.generateKeyPair(); + this.keyAgree = KeyAgreement.getInstance("DH"); + this.keyAgree.init(keyPair.getPrivate()); + DHPublicKey serverPublicKey = (DHPublicKey) keyPair.getPublic(); + + String hex = serverPublicKey.getY().toString(16); + + if (hex.length() == 192) { + invalid = false; + serverPublicKeyBytes = ByteUtils.stringHexToByteArray(hex); + } + if (tryCnt >= 5) + // Give java 4 tries to get a Public key of valid length. + throw new Exception("Not able to generate a valid length public key"); + ++tryCnt; + } + + // Calculate shared DH Secret Key + keyAgree.doPhase(clientPublicKey, true); + this.secretKey = keyAgree.generateSecret(); + this.serverPublicKey = serverPublicKeyBytes; + + // Now we have the server's publicKey and the common secretKey + } catch (Exception e) { + origin.disconnect(); + Logger.error(e); + } + + } + + public synchronized int initialize(AbstractConnection origin) { + long startTime = System.currentTimeMillis(); + int read = -1; + + // Read the data from connections socket channel + try { + read = origin.getSocketChannel().read(this.buffer); + } catch (IOException e) { + if (e.getLocalizedMessage() != null && !e.getLocalizedMessage().equals(MBServerStatics.EXISTING_CONNECTION_CLOSED) && !e.getLocalizedMessage().equals(MBServerStatics.RESET_BY_PEER)) + Logger.error(e); + origin.disconnect(); + return 0; + } + + if (read == -1) { + Logger.info("EOF on Socket Channel, Disconnecting " + origin.getLocalAddressAndPortAsString()); + origin.disconnect(); + return read; + } + + this.totalRead += read; + + if (this.totalRead > 100) + Logger.error("Possible Spam warning: " + + origin.getSocketChannel().socket().toString()); + + // Not all arrived yet, so wait for more + if (this.totalRead < 100) + return read; + + this.buffer.flip(); + this.buffer.getInt(); // get the length first & throw away value. + + byte[] PeerPubKeyEnc = new byte[96]; + this.buffer.get(PeerPubKeyEnc); + + this.calcKeys(origin, PeerPubKeyEnc); + + try { + byte[] sharedSecret = this.secretKey; + byte[] PubKeyEnc = this.serverPublicKey; + + // Cut DH SecretKey down to 16 bytes + System.arraycopy(sharedSecret, 0, secretKeyBytes, 0, 16); + + // Calculate Blowfish Secret Key and make ciphers streams. + this.BFKey = new SecretKeySpec(this.secretKeyBytes, "Blowfish"); + + // Initialize cipher and Ivecs. + // Ivecs must be run through the cipher once + // to prep the cipher for cfb mode. + this.cipher = Cipher.getInstance("Blowfish/ECB/NoPadding"); + this.cipher.init(Cipher.ENCRYPT_MODE, this.BFKey); + this.cipher.update(this.iVecEnc, 0, 8, this.iVecEnc, 0); + this.cipher.update(this.iVecDec, 0, 8, this.iVecDec, 0); + + // Send public key to peer + byte[] pubKeyLen = {0x00, 0x00, 0x00, 0x60}; // hex for 96 + ByteBuffer bb = ByteBuffer.wrap(pubKeyLen); + bb.position(bb.limit()); + origin.sendBB(bb); + bb = ByteBuffer.wrap(PubKeyEnc); + bb.position(bb.limit()); + origin.sendBB(bb); + } catch (Exception e) { + Logger.error(e); + origin.disconnect(); + return read; + } + /* + * //Send Secret Key to Login Server if (PortalServer.ServerType == + * "Login") { byte[] keyinfo = new byte[20]; keyinfo[0] = 0x11; + * keyinfo[1] = 0x11; keyinfo[2] = 0x11; keyinfo[3] = 0x11; for (int i = + * 0; i < 16; i++) keyinfo[i + 4] = ShortSharedSecret[i]; + * PortalPair.HandleOutput(keyinfo, 20); } + */ + this.initialized = true; + +// long endTime = System.currentTimeMillis(); +// +// long time = endTime - startTime; +// // Logger.debug("", "Authenticator took " + time + " ms to initialize."); + + return read; + } + + public synchronized void encrypt(final ByteBuffer dataIn, final ByteBuffer dataOut) { + try { + // Assume that if position != 0 that we need to flip. + if (dataIn.position() != 0) + dataIn.flip(); + + int count = dataIn.limit(); + + //Line up the iVecEncOffset.. fall through is intentional + if (iVecEncOffset != 0) + if ((this.iVecEncOffset + dataIn.limit()) < 8) { + //This handles cases where the net msg + offset won't reach 8 bytes total + //prevents BufferUnderflowException in small net messages. - + int newEncOffset = this.iVecEncOffset + dataIn.limit(); + for (int i = this.iVecEncOffset; i < newEncOffset; i++) { + this.iVecEnc[i] = (byte) (dataIn.get() ^ this.iVecEnc[i]); + dataOut.put(this.iVecEnc[i]); + } + this.iVecEncOffset = newEncOffset; + return; + } else + switch (iVecEncOffset) { + case 1: + this.iVecEnc[1] = (byte) (dataIn.get() ^ this.iVecEnc[1]); + dataOut.put(this.iVecEnc[1]); + case 2: + this.iVecEnc[2] = (byte) (dataIn.get() ^ this.iVecEnc[2]); + dataOut.put(this.iVecEnc[2]); + case 3: + this.iVecEnc[3] = (byte) (dataIn.get() ^ this.iVecEnc[3]); + dataOut.put(this.iVecEnc[3]); + case 4: + this.iVecEnc[4] = (byte) (dataIn.get() ^ this.iVecEnc[4]); + dataOut.put(this.iVecEnc[4]); + case 5: + this.iVecEnc[5] = (byte) (dataIn.get() ^ this.iVecEnc[5]); + dataOut.put(this.iVecEnc[5]); + case 6: + this.iVecEnc[6] = (byte) (dataIn.get() ^ this.iVecEnc[6]); + dataOut.put(this.iVecEnc[6]); + case 7: + this.iVecEnc[7] = (byte) (dataIn.get() ^ this.iVecEnc[7]); + dataOut.put(this.iVecEnc[7]); + count -= (8 - iVecEncOffset); + this.iVecEncOffset = 0; + this.cipher.update(this.iVecEnc, 0, 8, this.iVecEnc, 0); + } + + //Main loop - unrolled x8 + int loopCount = (count) >> 3; + for (int i = 0; i < loopCount; i++) { + + this.iVecEnc[0] = (byte) (dataIn.get() ^ this.iVecEnc[0]); + this.iVecEnc[1] = (byte) (dataIn.get() ^ this.iVecEnc[1]); + this.iVecEnc[2] = (byte) (dataIn.get() ^ this.iVecEnc[2]); + this.iVecEnc[3] = (byte) (dataIn.get() ^ this.iVecEnc[3]); + this.iVecEnc[4] = (byte) (dataIn.get() ^ this.iVecEnc[4]); + this.iVecEnc[5] = (byte) (dataIn.get() ^ this.iVecEnc[5]); + this.iVecEnc[6] = (byte) (dataIn.get() ^ this.iVecEnc[6]); + this.iVecEnc[7] = (byte) (dataIn.get() ^ this.iVecEnc[7]); + + dataOut.put(this.iVecEnc[0]); + dataOut.put(this.iVecEnc[1]); + dataOut.put(this.iVecEnc[2]); + dataOut.put(this.iVecEnc[3]); + dataOut.put(this.iVecEnc[4]); + dataOut.put(this.iVecEnc[5]); + dataOut.put(this.iVecEnc[6]); + dataOut.put(this.iVecEnc[7]); + this.cipher.update(this.iVecEnc, 0, 8, this.iVecEnc, 0); + } + + //Resync the iVecEncOffset to handle the remainder.. + this.iVecEncOffset = count % 8; + for (int i = 0; i < iVecEncOffset; i++) { + this.iVecEnc[i] = (byte) (dataIn.get() ^ this.iVecEnc[i]); + dataOut.put(this.iVecEnc[i]); + } + } catch (BufferUnderflowException e) { + if (dataIn != null && dataOut != null) + Logger.warn("Encrypt Error: (in)" + dataIn.toString() + " :: (out)" + dataOut.toString()); + Logger.error("ClientAuth.encrypt() -> Underflow" + e); + } catch (BufferOverflowException e) { + if (dataIn != null && dataOut != null) + Logger.warn("Encrypt Error: (in)" + dataIn.toString() + " :: (out)" + dataOut.toString()); + Logger.error("ClientAuth.encrypt() -> Overflow" + e); + } catch (Exception e) { + Logger.error("ClientAuth.encrypt()" + e); + } + } + + public synchronized void decrypt(ByteBuffer dataIn, ByteBuffer dataOut) { + try { // get lock + synchronized (dataIn) { // TODO is this lock needed? + + // Assume that if position != 0 that we need to flip. + if (dataIn.position() != 0) + dataIn.flip(); + + byte encryptedByte; + byte decryptedByte; + + for (int i = 0; i < dataIn.limit(); ++i) { + + // Get byte out of ByteBuffer + encryptedByte = dataIn.get(); + + // XOR it against the iVEC + decryptedByte = (byte) (encryptedByte ^ this.iVecDec[this.iVecDecOffset]); + + // put the decrypted byte into the outgoing ByteBuffer + dataOut.put(decryptedByte); + + // store the encrypted byte back into the iVEC + this.iVecDec[this.iVecDecOffset] = encryptedByte; + + // Increment ivecOffset + this.iVecDecOffset++; + + // Check to see if iVecOffset is at MAX. If so, reset + if (this.iVecDecOffset > 7) { + try { + this.cipher.update(this.iVecDec, 0, 8, + this.iVecDec, 0); + } catch (ShortBufferException e) { + // suck up this error + Logger.error(e); + } + this.iVecDecOffset = 0; + } + } + + + } + } catch (Exception e) { + Logger.error("ClientAuth.decrypt()" + e); + } + } + + private void initiateKey(byte[] clientPublicKeyBytes) { + + } + + /** + * @return the secretKeyBytes + */ + public byte[] getSecretKeyBytes() { + return secretKeyBytes; + } + + /** + * @return the initialized + */ + public boolean initialized() { + return initialized; + } + +} diff --git a/src/engine/net/client/ClientConnection.java b/src/engine/net/client/ClientConnection.java new file mode 100644 index 00000000..ad113a6a --- /dev/null +++ b/src/engine/net/client/ClientConnection.java @@ -0,0 +1,350 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client; + +import engine.Enum; +import engine.gameManager.ConfigManager; +import engine.gameManager.SessionManager; +import engine.job.JobScheduler; +import engine.jobs.DisconnectJob; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.Network; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.login.LoginErrorMsg; +import engine.objects.Account; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import engine.session.SessionID; +import org.pmw.tinylog.Logger; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SocketChannel; +import java.util.concurrent.locks.ReentrantLock; + +public class ClientConnection extends AbstractConnection { + + private final ClientAuthenticator crypto; + private final String clientIpAddress; + public String machineID; + public long guildtreespam = 0; + public long ordernpcspam = 0; + public ReentrantLock trainLock = new ReentrantLock(); + public ReentrantLock sellLock = new ReentrantLock(); + public ReentrantLock buyLock = new ReentrantLock(); + public boolean desyncDebug = false; + public byte[] lastByteBuffer; + protected SessionID sessionID = null; + private byte cryptoInitTries = 0; + + public ClientConnection(ClientConnectionManager connMan, + SocketChannel sockChan) { + super(connMan, sockChan, true); + this.crypto = new ClientAuthenticator(this); + + this.clientIpAddress = sockChan.socket().getRemoteSocketAddress() + .toString().replace("/", "").split(":")[0]; + } + + @Override + protected boolean _sendMsg(AbstractNetMsg msg) { + try { + msg.setOrigin(this); + ByteBuffer bb = msg.serialize(); + + // Application protocol logging toggled via + // DevCmd: netdebug on|off + + if (MBServerStatics.DEBUG_PROTOCOL) + applicationProtocolLogger(msg, MessageSource.SOURCE_SERVER); + + boolean retval = this.sendBB(bb); + Network.byteBufferPool.putBuffer(bb);//return here. + + return retval; + + } catch (Exception e) { // Catch-all + Logger.error(e); + return false; + } + } + + /** + * Sending a NetMsg to the client involves NOT including a dataLen parameter + * and also involves encrypting the data. + */ + @Override + protected boolean _sendBB(ByteBuffer bb) { + boolean useCrypto = this.crypto.initialized(); + boolean retVal; + + // Logger.debug("useCrypto: " + useCrypto + ". bb.cap(): " + + // bb.capacity()); + if (useCrypto == false) + retVal = super._sendBB(bb); + else { + if (bb == null) + Logger.error("Incoming bb is null"); + ByteBuffer encrypted = Network.byteBufferPool.getBufferToFit(bb.capacity()); + if (encrypted == null) + Logger.error("Encrypted bb is null"); + this.crypto.encrypt(bb, encrypted); + retVal = super._sendBB(encrypted); + } + + return retVal; + } + + /** + * Receiving data from a client involves the initial Crypto Key Exchange, + * waiting for a complete NetMsg to arrive using an accumulation factory and + * decrypting the data. + */ + //FIXME the int return value on this means nothing! Clean it up! + @Override + protected int read() { + + if (readLock.tryLock()) + try { + + // First and foremost, check to see if we the Crypto is initted yet + if (!this.crypto.initialized()) + this.crypto.initialize(this); + + if (!this.crypto.initialized()) { + ++this.cryptoInitTries; + if (this.cryptoInitTries >= MBServerStatics.MAX_CRYPTO_INIT_TRIES) { + Logger.info("Failed to initialize after " + + MBServerStatics.MAX_CRYPTO_INIT_TRIES + + " tries. Disconnecting."); + this.disconnect(); + } + return 0; + } + + // check to see if SessionID == null; + if (this.sessionID == null) + this.sessionID = new SessionID(this.crypto.getSecretKeyBytes()); + + // Get ByteBuffers out of pool. + ByteBuffer bb = Network.byteBufferPool.getBuffer(16); + ByteBuffer decrypted = Network.byteBufferPool.getBuffer(16); + // ByteBuffer bb = ByteBuffer.allocate(1024 * 4); + + int totalBytesRead = 0; + int lastRead = 0; + do { + try { + bb.clear(); + decrypted.clear(); + lastRead = this.sockChan.read(bb); + // On EOF on the SocketChannel, disconnect. + if (lastRead <= -1) { + this.disconnect(); + break; + } + + if (lastRead == 0) + continue; + + // ByteBuffer decrypted = ByteBuffer.allocate(lastRead); + this.crypto.decrypt(bb, decrypted); + this.factory.addData(decrypted); + + + this.checkInternalFactory(); + + totalBytesRead += lastRead; + + + } catch (NotYetConnectedException e) { + Logger.error(e.getLocalizedMessage()); + totalBytesRead = -1; // Set error retVal + break; + + } catch (ClosedChannelException e) { + // TODO Should a closed channel be logged or just cleaned up? + // this.logEXCEPTION(e); + this.disconnect(); + totalBytesRead = -1; // Set error retVal + break; + + } catch (IOException e) { + if (e.getLocalizedMessage() != null && (!e.getLocalizedMessage().equals(MBServerStatics.EXISTING_CONNECTION_CLOSED) && !e.getLocalizedMessage().equals(MBServerStatics.RESET_BY_PEER))) { + Logger.info("Error Reading message opcode " + this.lastOpcode); + Logger.error(e); + } + this.disconnect(); + totalBytesRead = -1; // Set error retVal + break; + + } catch (Exception e) { + Logger.info("Error Reading message opcode " + this.lastOpcode); + Logger.error(e); + totalBytesRead = -1; // Set error retVal + this.disconnect(); + break; + } + } + + while (lastRead > 0); + + Network.byteBufferPool.putBuffer(bb); + Network.byteBufferPool.putBuffer(decrypted); + + return totalBytesRead; + + } finally { + readLock.unlock(); + } + else { + Logger.debug("Another thread already has a read lock! Skipping."); + return 0; + } + } + + @Override + public void disconnect() { + super.disconnect(); + try { + + if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER)) + ConfigManager.worldServer.removeClient(this); + else + ConfigManager.loginServer.removeClient(this); + + // TODO There has to be a more direct way to do this... + SessionManager.remSession( + SessionManager.getSession(sessionID)); + } catch (NullPointerException e) { + Logger + .error( + "Tried to remove improperly initialized session. Skipping." + + e); + } + } + + public void forceDisconnect() { + super.disconnect(); + } + + public SessionID getSessionID() { + return sessionID; + } + + /* + * Getters n setters + */ + + public byte[] getSecretKeyBytes() { + return this.crypto.getSecretKeyBytes(); + } + + /* + * Convenience getters for SessionManager + */ + public Account getAccount() { + return SessionManager.getAccount(this); + } + + public PlayerCharacter getPlayerCharacter() { + return SessionManager.getPlayerCharacter(this); + } + + @Override + public boolean handleClientMsg(ClientNetMsg msg) { + + Protocol protocolMsg = msg.getProtocolMsg(); + + switch (protocolMsg) { + case KEEPALIVESERVERCLIENT: + this.setLastKeepAliveTime(); + break; + // case ClientOpcodes.OpenVault: + // case ClientOpcodes.Random: + // case ClientOpcodes.DoorTryOpen: + // case ClientOpcodes.SetSelectedObect: + // case ClientOpcodes.MoveObjectToContainer: + // case ClientOpcodes.ToggleSitStand: + // case ClientOpcodes.SocialChannel: + // case ClientOpcodes.OpenFriendsCondemnList: + case SELLOBJECT: + this.setLastMsgTime(); + break; + case MOVETOPOINT: + case ARCCOMBATMODEATTACKING: + this.setLastMsgTime(); + break; + default: + this.setLastMsgTime(); + break; + } + + // Application protocol logging toggled via + // DevCmd: netdebug on|off + + if (MBServerStatics.DEBUG_PROTOCOL) + applicationProtocolLogger(msg, MessageSource.SOURCE_CLIENT); + + return ConfigManager.handler.handleClientMsg(msg); // *** Refactor : Null check then call + } + + private void applicationProtocolLogger(AbstractNetMsg msg, MessageSource origin) { + + String outString = ""; + PlayerCharacter tempPlayer = null; + + // Log the protocolMsg + if (origin == MessageSource.SOURCE_CLIENT) + outString = " Incoming protocolMsg: "; + else + outString = " Outgoing protocolMsg: "; + + Logger.info(outString + + Integer.toHexString(msg.getProtocolMsg().opcode) + + '/' + msg.getProtocolMsg()); + + // Dump message contents using reflection + tempPlayer = this.getPlayerCharacter(); + outString = ""; + outString += (tempPlayer == null) ? "PlayerUnknown" : tempPlayer.getFirstName() + ' ' + + msg.toString(); + Logger.info(outString); + } + // Method logs detailed information about application + // protocol traffic. Toggled at runtime via the + // DevCmd netdebug on|off + + public void kickToLogin(int errCode, String message) { + + LoginErrorMsg lom = new LoginErrorMsg(errCode, message); + + if (!sendMsg(lom)) + Logger.error("Failed to send message"); // TODO Do we just accept this failure to send Msg? + + DisconnectJob dj = new DisconnectJob(this); + JobScheduler.getInstance().scheduleJob(dj, 250); + + } + + public final String getClientIpAddress() { + return this.clientIpAddress; + } + + // Enumeration of a message's origin for logging purposes + private enum MessageSource { + + SOURCE_CLIENT, + SOURCE_SERVER + } +} diff --git a/src/engine/net/client/ClientConnectionManager.java b/src/engine/net/client/ClientConnectionManager.java new file mode 100644 index 00000000..0ccf5fb8 --- /dev/null +++ b/src/engine/net/client/ClientConnectionManager.java @@ -0,0 +1,35 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client; + +import engine.net.AbstractConnection; +import engine.net.AbstractConnectionManager; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.channels.SocketChannel; + +public class ClientConnectionManager extends AbstractConnectionManager { + + public ClientConnectionManager(String threadName, InetAddress hostAddress, int port) + throws IOException { + super(threadName, hostAddress, port); + } + + @Override + protected AbstractConnection getNewIncomingConnection(SocketChannel sockChan) { + return new ClientConnection(this, sockChan); + } + + @Override + protected AbstractConnection getNewOutgoingConnection(SocketChannel sockChan) { + return new ClientConnection(this, sockChan); + } +} diff --git a/src/engine/net/client/ClientMessagePump.java b/src/engine/net/client/ClientMessagePump.java new file mode 100644 index 00000000..d31a63c3 --- /dev/null +++ b/src/engine/net/client/ClientMessagePump.java @@ -0,0 +1,2099 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client; + +import engine.Enum.*; +import engine.InterestManagement.WorldGrid; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.RefreshGroupJob; +import engine.jobs.StuckJob; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.NetMsgHandler; +import engine.net.client.handlers.AbstractClientMsgHandler; +import engine.net.client.msg.*; +import engine.net.client.msg.chat.AbstractChatMsg; +import engine.net.client.msg.commands.ClientAdminCommandMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import engine.server.world.WorldServer; +import engine.session.Session; +import engine.util.StringUtils; +import org.pmw.tinylog.Logger; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; + +/** + * @author: + * @summary: This class is the mainline router for application protocol + * messages received by the client. + */ + +public class ClientMessagePump implements NetMsgHandler { + + // Instance variable declaration + + private final WorldServer server; + + public ClientMessagePump(WorldServer server) { + super(); + this.server = server; + } + + /* + * Incoming client protocol message are processed here + */ + + private static void WhoRequest(WhoRequestMsg msg, ClientConnection origin) { + + // Handle /who request + PlayerCharacter pc = origin.getPlayerCharacter(); + + if (pc == null) + return; + + if (pc.getTimeStamp("WHO") > System.currentTimeMillis()) { + ErrorPopupMsg.sendErrorMsg(pc, "Who too fast! Please wait 3 seconds."); + return; + } + + WhoResponseMsg.HandleResponse(msg.getSet(), msg.getFilterType(), msg.getFilter(), origin); + pc.getTimestamps().put("WHO", System.currentTimeMillis() + 3000); + } + + // *** Refactor need to figure this out. + // Commented out for some reson or another. + + private static void runWalkSitStand(UpdateStateMsg msg, ClientConnection origin) throws MsgSendException { + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + if (pc == null) + return; + + pc.update(); + if (msg.getSpeed() == 2) + pc.setWalkMode(false); + else + pc.setWalkMode(true); + DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + + private static void toggleLfgRecruiting(ToggleLfgRecruitingMsg msg, ClientConnection origin) throws MsgSendException { + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + if (pc == null) + return; + int num = msg.toggleLfgRecruiting(); + if (num == 1) + pc.toggleLFGroup(); + else if (num == 2) + pc.toggleLFGuild(); + else if (num == 3) + pc.toggleRecruiting(); + UpdateStateMsg rwss = new UpdateStateMsg(); + rwss.setPlayer(pc); + DispatchMessage.dispatchMsgToInterestArea(pc, rwss, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + + private static void toggleSitStand(ToggleSitStandMsg msg, ClientConnection origin) throws MsgSendException { + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + if (pc == null) + return; + + pc.update(); + + pc.setSit(msg.toggleSitStand()); + + // cancel effects that break on sit + if (pc.isSit()) { + pc.setCombat(false); + pc.cancelOnSit(); + } + + UpdateStateMsg rwss = new UpdateStateMsg(); + if (pc.isSit()) { + pc.setCombat(false); + rwss.setAware(1); + } + rwss.setPlayer(pc); + + DispatchMessage.dispatchMsgToInterestArea(pc, rwss, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + + private static void targetObject(TargetObjectMsg msg, ClientConnection origin) { + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + if (pc == null) + return; + + // TODO improve this later. hacky way to make sure player ingame is + // active. + + if (!pc.isActive()) + pc.setActive(true); + + pc.setLastTarget(GameObjectType.values()[msg.getTargetType()], msg.getTargetID()); + } + + private static void social(SocialMsg msg, ClientConnection origin) throws MsgSendException { + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + if (pc == null) + return; + DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, true); + } + + private static void TransferItemFromEquipToInventory(TransferItemFromEquipToInventoryMsg msg, ClientConnection origin) { + PlayerCharacter pc = origin.getPlayerCharacter(); + if (pc == null) + return; + + CharacterItemManager itemManager = pc.getCharItemManager(); + if (itemManager == null) + return; + + int slot = msg.getSlotNumber(); + + Item i = itemManager.getItemFromEquipped(slot); + if (i == null) + return; + + if (!itemManager.doesCharOwnThisItem(i.getObjectUUID())) + return; + + //dupe check + if (!i.validForEquip(origin, pc, itemManager)) + return; + + if (i.containerType == ItemContainerType.EQUIPPED) + itemManager.moveItemToInventory(i); + + int ItemType = i.getObjectType().ordinal(); + int ItemID = i.getObjectUUID(); + for (String name : i.getEffects().keySet()) { + Effect eff = i.getEffects().get(name); + if (eff == null) + return; + ApplyEffectMsg pum = new ApplyEffectMsg(); + pum.setEffectID(eff.getEffectToken()); + pum.setSourceType(pc.getObjectType().ordinal()); + pum.setSourceID(pc.getObjectUUID()); + pum.setTargetType(pc.getObjectType().ordinal()); + pum.setTargetID(pc.getObjectUUID()); + pum.setNumTrains(eff.getTrains()); + pum.setUnknown05(1); + pum.setUnknown02(2); + pum.setUnknown06((byte) 1); + pum.setEffectSourceType(ItemType); + pum.setEffectSourceID(ItemID); + pum.setDuration(-1); + + + DispatchMessage.dispatchMsgToInterestArea(pc, pum, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + ; + + } + // Update player formulas + pc.applyBonuses(); + DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + + } + + private static void TransferItemFromInventoryToEquip(TransferItemFromInventoryToEquipMsg msg, ClientConnection origin) { + PlayerCharacter pc = origin.getPlayerCharacter(); + if (pc == null) + return; + + CharacterItemManager itemManager = pc.getCharItemManager(); + if (itemManager == null) { + forceTransferFromEquipToInventory(msg, origin, "Can't find your item manager"); + return; + } + + int uuid = msg.getUUID(); + int slot = msg.getSlotNumber(); + //System.out.println("loading to slot: " + slot); + + Item i = itemManager.getItemByUUID(uuid); + + if (i == null) { + forceTransferFromEquipToInventory(msg, origin, "Item not found in your item manager"); + return; + } + + if (!itemManager.doesCharOwnThisItem(i.getObjectUUID())) { + forceTransferFromEquipToInventory(msg, origin, "You do not own this item"); + return; + } + + //dupe check + if (!i.validForInventory(origin, pc, itemManager)) + return; + + if (i.containerType == ItemContainerType.INVENTORY) { + if (!itemManager.equipItem(i, (byte) slot)) { + forceTransferFromEquipToInventory(msg, origin, "Failed to transfer item."); + return; + } + } else { + forceTransferFromEquipToInventory(msg, origin, "This item is not in your inventory"); + return; + } + + // Update player formulas + pc.applyBonuses(); + DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + + + for (String name : i.getEffects().keySet()) { + Effect eff = i.getEffects().get(name); + if (eff == null) + return; + + ApplyEffectMsg pum = new ApplyEffectMsg(); + pum.setEffectID(eff.getEffectToken()); + pum.setSourceType(pc.getObjectType().ordinal()); + pum.setSourceID(pc.getObjectUUID()); + pum.setTargetType(pc.getObjectType().ordinal()); + pum.setTargetID(pc.getObjectUUID()); + pum.setNumTrains(eff.getTrains()); + pum.setUnknown05(1); + pum.setUnknown06((byte) 1); + pum.setEffectSourceType(i.getObjectType().ordinal()); + pum.setEffectSourceID(i.getObjectUUID()); + pum.setDuration(-1); + + DispatchMessage.dispatchMsgToInterestArea(pc, pum, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + ; + } + } + + //call this if the transfer fails server side to kick the item back to inventory from equip + private static void forceTransferFromEquipToInventory(TransferItemFromInventoryToEquipMsg msg, ClientConnection origin, String reason) { + + PlayerCharacter pc = origin.getPlayerCharacter(); + + if (pc == null) + return; + + TransferItemFromEquipToInventoryMsg back = new TransferItemFromEquipToInventoryMsg(pc, msg.getSlotNumber()); + Dispatch dispatch = Dispatch.borrow(pc, back); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ChatManager.chatInfoError(pc, "Can't equip item: " + reason); + } + + public static Boolean NPCVaultBankRangeCheck(PlayerCharacter pc, ClientConnection origin, String bankorvault) { + + if (pc == null) + return false; + + NPC npc = pc.getLastNPCDialog(); + + if (npc == null) + return false; + + // System.out.println(npc.getContract().getName()); + // last npc must be either a banker or vault keeper + + if (bankorvault.equals("vault")) { + if (npc.getContract().getContractID() != 861) + return false; + } else + // assuming banker + + if (!npc.getContract().getName().equals("Bursar")) + return false; + + if (pc.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { + ErrorPopupMsg.sendErrorPopup(pc, 14); + return false; + } else + return true; + + } + + private static void transferItemFromInventoryToBank(TransferItemFromInventoryToBankMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + if (!NPCVaultBankRangeCheck(player, origin, "bank")) + return; + + CharacterItemManager itemManager = player.getCharItemManager(); + + if (itemManager == null) + return; + + if (itemManager.getBankWeight() > 500) { + ErrorPopupMsg.sendErrorPopup(player, 21); + return; + } + + int uuid = msg.getUUID(); + + Item item = itemManager.getItemByUUID(uuid); + + if (item == null) + return; + + //dupe check WTF CHECK BUT NO LOGGING? + + if (!item.validForInventory(origin, player, itemManager)) + return; + + if (item.containerType == ItemContainerType.INVENTORY && itemManager.isBankOpen()) + if (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD)) { + if (!itemManager.moveGoldToBank(item, msg.getNumItems())) + return; + UpdateGoldMsg goldMes = new UpdateGoldMsg(player); + goldMes.configure(); + + dispatch = Dispatch.borrow(player, goldMes); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } else { + + if (!itemManager.hasRoomBank(item.getItemBase().getWeight())) + return; + + if (!itemManager.moveItemToBank(item)) + return; + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + } + + private static void transferItemFromBankToInventory(TransferItemFromBankToInventoryMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + if (!NPCVaultBankRangeCheck(player, origin, "bank")) + return; + + CharacterItemManager itemManager = player.getCharItemManager(); + + if (itemManager == null) + return; + + int uuid = msg.getUUID(); + + Item item = itemManager.getItemByUUID(uuid); + + if (item == null) + return; + + //dupe check + // WTF Checking but not logging? + + if (!item.validForBank(origin, player, itemManager)) + return; + + if (item.containerType == ItemContainerType.BANK && itemManager.isBankOpen() == false) + return; + + if (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD)) { + + if (!itemManager.moveGoldToInventory(item, msg.getNumItems())) + return; + + UpdateGoldMsg goldMes = new UpdateGoldMsg(player); + goldMes.configure(); + + dispatch = Dispatch.borrow(player, goldMes); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + return; + } + + // Not gold, process update here + + if (!itemManager.hasRoomInventory(item.getItemBase().getWeight())) + return; + + if (itemManager.moveItemToInventory(item) == false) + return; + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + private static void transferItemFromVaultToInventory(TransferItemFromVaultToInventoryMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + if (player.getAccount() == null) + return; + player.getAccount().transferItemFromVaultToInventory(msg, origin); + + + } + + //call this if the transfer fails server side to kick the item back to inventory from vault + public static void forceTransferFromInventoryToVault(TransferItemFromVaultToInventoryMsg msg, ClientConnection origin, String reason) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + TransferItemFromInventoryToVaultMsg back = new TransferItemFromInventoryToVaultMsg(msg); + dispatch = Dispatch.borrow(player, back); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ChatManager.chatInfoError(player, "Can't transfer to inventory: " + reason); + } + + private static void transferItemFromInventoryToVault(TransferItemFromInventoryToVaultMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + if (player.getAccount() == null) + return; + player.getAccount().transferItemFromInventoryToVault(msg, origin); + + } + + //call this if the transfer fails server side to kick the item back to vault from inventory + public static void forceTransferFromVaultToInventory(TransferItemFromInventoryToVaultMsg msg, ClientConnection origin, String reason) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + TransferItemFromVaultToInventoryMsg back = new TransferItemFromVaultToInventoryMsg(msg); + dispatch = Dispatch.borrow(player, back); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ChatManager.chatInfoError(player, "Can't transfer to vault: " + reason); + } + + private static void transferGoldFromVaultToInventory(TransferGoldFromVaultToInventoryMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + + + if (player == null) + return; + + Account account = player.getAccount(); + + if (account == null) + return; + + account.transferGoldFromVaultToInventory(msg, origin); + } + + private static void transferGoldFromInventoryToVault(TransferGoldFromInventoryToVaultMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + Account account = player.getAccount(); + + if (account == null) + return; + + account.transferGoldFromInventoryToVault(msg, origin); + + } + + private static void DeleteItem(DeleteItemMsg msg, ClientConnection origin) { + + CharacterItemManager itemManager = origin.getPlayerCharacter().getCharItemManager(); + int uuid = msg.getUUID(); + + + PlayerCharacter sourcePlayer = origin.getPlayerCharacter(); + + if (sourcePlayer == null) + return; + + if (!sourcePlayer.isAlive()) + return; + + Item i = Item.getFromCache(msg.getUUID()); + + if (i == null) + return; + + if (!itemManager.doesCharOwnThisItem(i.getObjectUUID())) + return; + + if (!itemManager.inventoryContains(i)) + return; + + if (i.isCanDestroy()) + if (itemManager.delete(i) == true) { + Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + } + + private static void ackBankWindowOpened(AckBankWindowOpenedMsg msg, ClientConnection origin) { + // According to the Wiki, the client should not send this message. + // Log the instance to investigate, and modify Wiki accordingly. + Logger.error(msg.toString()); + } + + private static void modifyStat(ModifyStatMsg msg, ClientConnection origin) { + + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + + if (pc == null) + return; + + int type = msg.getType(); + + switch (type) { + case MBServerStatics.STAT_STR_ID: + pc.addStr(msg.getAmount()); + break; + case MBServerStatics.STAT_DEX_ID: + pc.addDex(msg.getAmount()); + break; + case MBServerStatics.STAT_CON_ID: + pc.addCon(msg.getAmount()); + break; + case MBServerStatics.STAT_INT_ID: + pc.addInt(msg.getAmount()); + break; + case MBServerStatics.STAT_SPI_ID: + pc.addSpi(msg.getAmount()); + break; + } + } + + // called when player clicks respawn button + private static void respawn(RespawnMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin); + + if (sourcePlayer == null) + return; + + if (msg.getObjectType() != sourcePlayer.getObjectType().ordinal() || msg.getObjectID() != sourcePlayer.getObjectUUID()) { + Logger.error("Player " + sourcePlayer.getObjectUUID() + " respawning character of id " + msg.getObjectType() + ' ' + + msg.getObjectID()); + return; + } + + if (sourcePlayer.isAlive()) { + Logger.error("Player " + sourcePlayer.getObjectUUID() + " respawning while alive"); + return; + } + // ResetAfterDeath player + sourcePlayer.respawnLock.writeLock().lock(); + try { + sourcePlayer.respawn(true, false, true); + + } catch (Exception e) { + Logger.error(e); + } finally { + sourcePlayer.respawnLock.writeLock().unlock(); + + } + // Echo ResetAfterDeath message back + msg.setPlayerHealth(sourcePlayer.getHealth()); + // TODO calculate any experience loss before this point + msg.setPlayerExp(sourcePlayer.getExp() + sourcePlayer.getOverFlowEXP()); + Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + MoveToPointMsg moveMsg = new MoveToPointMsg(); + moveMsg.setPlayer(sourcePlayer); + moveMsg.setStartCoord(sourcePlayer.getLoc()); + moveMsg.setEndCoord(sourcePlayer.getLoc()); + moveMsg.setInBuilding(-1); + moveMsg.setUnknown01(-1); + + dispatch = Dispatch.borrow(sourcePlayer, moveMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + MovementManager.sendRWSSMsg(sourcePlayer); + + // refresh the whole group with what just happened + JobScheduler.getInstance().scheduleJob(new RefreshGroupJob(sourcePlayer), MBServerStatics.LOAD_OBJECT_DELAY); + } + + private static void lootWindowRequest(LootWindowRequestMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + + if (pc == null) + return; + + if (!pc.isAlive()) + return; + + if (msg.getSourceType() != pc.getObjectType().ordinal() || msg.getSourceID() != pc.getObjectUUID()) { + Logger.error("Player " + pc.getObjectUUID() + " looting from character of id " + + msg.getSourceType() + ' ' + msg.getSourceID()); + return; + } + + if (pc.getAltitude() > 0) + return; + if (!pc.isAlive()) { + return; + } + + + LootWindowResponseMsg lwrm = null; + GameObjectType targetType = GameObjectType.values()[msg.getTargetType()]; + AbstractCharacter characterTarget = null; + Corpse corpseTarget = null; + + switch (targetType) { + case PlayerCharacter: + + characterTarget = PlayerCharacter.getFromCache(msg.getTargetID()); + if (characterTarget == null) + return; + if (characterTarget.isAlive()) + return; + if (pc.getLoc().distanceSquared2D(characterTarget.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) { + ErrorPopupMsg.sendErrorMsg(pc, "You are too far away to loot this corpse."); + + Logger.info(pc.getFirstName() + " tried looting at " + pc.getLoc().distance2D(characterTarget.getLoc()) + " distance."); + return; + } + lwrm = new LootWindowResponseMsg(characterTarget.getObjectType().ordinal(), characterTarget.getObjectUUID(), characterTarget.getInventory(true)); + break; + case NPC: + characterTarget = NPC.getFromCache(msg.getTargetID()); + if (characterTarget == null) + return; + break; + case Mob: + characterTarget = Mob.getFromCache(msg.getTargetID()); + if ((characterTarget == null) || characterTarget.isAlive()) { + return; + } + + if (pc.getLoc().distanceSquared2D(characterTarget.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) { + ErrorPopupMsg.sendErrorMsg(pc, "You are too far away to loot this corpse."); + + Logger.info(pc.getFirstName() + " tried looting at " + pc.getLoc().distance2D(characterTarget.getLoc()) + " distance."); + + if (!((Mob) characterTarget).isLootSync()) { + + ((Mob) characterTarget).setLootSync(true); + WorldGrid.updateObject(characterTarget, pc); + } + + + return; + } + + lwrm = new LootWindowResponseMsg(characterTarget.getObjectType().ordinal(), characterTarget.getObjectUUID(), characterTarget.getInventory()); + break; + case Corpse: + corpseTarget = Corpse.getCorpse(msg.getTargetID()); + + if ((corpseTarget == null)) { + return; + } + + if (pc.getLoc().distanceSquared(corpseTarget.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) { + ErrorPopupMsg.sendErrorMsg(pc, "You are too far away to loot this corpse."); + + Logger.info(pc.getFirstName() + " tried looting at " + pc.getLoc().distance2D(characterTarget.getLoc()) + " distance."); + return; + } + lwrm = new LootWindowResponseMsg(corpseTarget.getObjectType().ordinal(), msg.getTargetID(), corpseTarget.getInventory()); + break; + } + + if (lwrm == null) + return; + + DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + Dispatch dispatch = Dispatch.borrow(pc, lwrm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + } + + private static void loot(LootMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + if (player == null) + return; + + if (!player.isAlive()) + return; + + Item item = msg.getItem(); + + if (item == null) + return; + + if (item.lootLock.tryLock()) { + try { + Item itemRet = null; + // get current owner + int targetType = msg.getTargetType(); + int targetID = msg.getTargetID(); + + if (targetType == GameObjectType.PlayerCharacter.ordinal() || targetType == GameObjectType.Mob.ordinal() || targetType == GameObjectType.Corpse.ordinal()) { + } else { //needed for getting contracts for some reason + targetType = msg.getSourceID2(); + targetID = msg.getUnknown01(); + } + + //can't loot while flying + if (player.getAltitude() > 0) + return; + + AbstractCharacter tar = null; + Corpse corpse = null; + + if (targetType == GameObjectType.PlayerCharacter.ordinal() || targetType == GameObjectType.Mob.ordinal()) { + + if (targetType == GameObjectType.PlayerCharacter.ordinal()) { + tar = PlayerCharacter.getFromCache(targetID); + + if (tar == null) + return; + + if (player.getObjectUUID() != tar.getObjectUUID() && ((PlayerCharacter) tar).isInSafeZone()) + return; + + } else if (targetType == GameObjectType.NPC.ordinal()) + tar = NPC.getFromCache(targetID); + else if (targetType == GameObjectType.Mob.ordinal()) + tar = Mob.getFromCache(targetID); + if (tar == null) + return; + + if (tar.equals(player)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot loot this item."); + return; + } + + + if (player.getLoc().distanceSquared2D(tar.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) { + ErrorPopupMsg.sendErrorMsg(player, "You are too far away to loot this corpse."); + + Logger.info(player.getFirstName() + " tried looting at " + player.getLoc().distance2D(tar.getLoc()) + " distance."); + return; + } + + //can't loot from someone who is alive. + if (AbstractWorldObject.IsAbstractCharacter(tar)) { + if (tar.isAlive()) + return; + // Logger.error("WorldServer.loot", "Looting from live player"); + } + + if (!GroupManager.goldSplit(player, item, origin, tar)) { + + if (tar.getCharItemManager() != null) { + + itemRet = tar.getCharItemManager().lootItemFromMe(item, player, origin); + + //Take equipment off mob + if (tar.getObjectType() == GameObjectType.Mob && itemRet != null) { + Mob mobTarget = (Mob) tar; + + if (item != null && item.getObjectType() == GameObjectType.MobLoot) { + + for (MobEquipment equip : mobTarget.getEquip().values()) { + + TransferItemFromEquipToInventoryMsg back = new TransferItemFromEquipToInventoryMsg(mobTarget, equip.getSlot()); + DispatchMessage.dispatchMsgToInterestArea(mobTarget, back, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + + LootMsg lootMsg = new LootMsg(0, 0, tar.getObjectType().ordinal(), tar.getObjectUUID(), equip); + Dispatch dispatch = Dispatch.borrow(player, lootMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + break; + } + } + } + } + + } + } else if (targetType == GameObjectType.Corpse.ordinal()) { + corpse = Corpse.getCorpse(targetID); + if (corpse == null) + return; + + if (player.getLoc().distanceSquared2D(corpse.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) { + ErrorPopupMsg.sendErrorMsg(player, "You are too far away to loot this corpse."); + + Logger.info(player.getFirstName() + " tried looting at " + player.getLoc().distance2D(corpse.getLoc()) + " distance."); + return; + } + + + //can't loot other players in safe zone. + if (corpse.getBelongsToType() == GameObjectType.PlayerCharacter.ordinal()) { + + if (player.getObjectUUID() == corpse.getBelongsToID()) + itemRet = corpse.lootItem(item, player); + else if (!GroupManager.goldSplit(player, item, origin, corpse)) { + itemRet = corpse.lootItem(item, player); + + } + + if (itemRet == null) + return; + + + if (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD)) { + // this is done to prevent the temporary goldItem item + // (from the mob) from appearing in player's inventory. + // It also updates the goldItem quantity display + UpdateGoldMsg updateTargetGold = null; + + + if (corpse != null) + updateTargetGold = new UpdateGoldMsg(corpse); + + updateTargetGold.configure(); + DispatchMessage.dispatchMsgToInterestArea(corpse, updateTargetGold, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + + UpdateGoldMsg ugm = new UpdateGoldMsg(player); + ugm.configure(); + Dispatch dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + // respond back loot message. Try sending to everyone. + + } else { + + DispatchMessage.dispatchMsgToInterestArea(corpse, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, true); + + + //player.getCharItemManager().updateInventory(); + } + + //TODO send group loot message if player is grouped and visible + Group group = GroupManager.getGroup(player); + + if (group != null && group.getSplitGold() && (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD) == false)) { + String name = item.getName(); + String text = player.getFirstName() + " has looted " + name + '.'; + ChatManager.chatGroupInfoCanSee(player, text); + } + + return; + } + + + } else + return; + + + if (itemRet == null) { + return; + } + + if (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD)) { + // this is done to prevent the temporary goldItem item + // (from the mob) from appearing in player's inventory. + // It also updates the goldItem quantity display + UpdateGoldMsg updateTargetGold = null; + + if (tar != null) + updateTargetGold = new UpdateGoldMsg(tar); + else if (corpse != null) + updateTargetGold = new UpdateGoldMsg(corpse); + + updateTargetGold.configure(); + DispatchMessage.dispatchMsgToInterestArea(tar, updateTargetGold, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + UpdateGoldMsg ugm = new UpdateGoldMsg(player); + ugm.configure(); + Dispatch dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + // respond back loot message. Try sending to everyone. + + } else { + msg.setSourceType1(0); + msg.setSourceType2(0); + msg.setSourceID1(0); + msg.setSourceID2(0); + Dispatch dispatch = Dispatch.borrow(player, msg); + //DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + DispatchMessage.dispatchMsgToInterestArea(tar, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, true); + LootMsg newItemMsg = new LootMsg(GameObjectType.PlayerCharacter.ordinal(), player.getObjectUUID(), 0, 0, itemRet); + dispatch = Dispatch.borrow(player, newItemMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + //player.getCharItemManager().updateInventory(); + } + + //TODO send group loot message if player is grouped and visible + Group group = GroupManager.getGroup(player); + + if (group != null && group.getSplitGold() && (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD) == false)) { + String name = item.getName(); + String text = player.getFirstName() + " has looted " + name + '.'; + ChatManager.chatGroupInfoCanSee(player, text); + } + } catch (Exception e) { + Logger.info(e.getMessage()); + } finally { + item.lootLock.unlock(); + } + } + + + } + + // called when player types /show + private static void show(ShowMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + + if (pc == null) + return; + + int targetType = msg.getTargetType(); + AbstractCharacter tar = null; + + if (targetType == GameObjectType.PlayerCharacter.ordinal()) + tar = PlayerCharacter.getFromCache(msg.getTargetID()); + else if (targetType == GameObjectType.NPC.ordinal()) + tar = NPC.getFromCache(msg.getTargetID()); + else if (targetType == GameObjectType.Mob.ordinal()) + tar = Mob.getFromCache(msg.getTargetID()); + + if (tar == null || !tar.isAlive() || !tar.isActive()) + return; + + msg.setUnknown01(pc.getLoc()); + msg.setUnknown02(pc.getLoc()); + msg.setRange01(pc.getRange()); + msg.setUnknown03(tar.getLoc()); + msg.setUnknown04(tar.getLoc()); + msg.setRange01(tar.getRange()); + + Dispatch dispatch = Dispatch.borrow(pc, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + private static void ViewResourcesMessage(ViewResourcesMessage msg, ClientConnection origin) throws SQLException { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return; + + Guild guild = player.getGuild(); + City city = guild.getOwnedCity(); + + if (city == null) + return; + + Building warehouse = BuildingManager.getBuilding(city.getWarehouseBuildingID()); + + if (warehouse == null) + return; + + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + vrm.setWarehouseBuilding(warehouse); + vrm.setGuild(player.getGuild()); + vrm.configure(); + + Dispatch dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + private static void randomRoll(RandomMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null || !source.isAlive()) + return; + + //2 second cooldown on random rolls + long lastRandom = source.getTimeStamp("RandomRoll"); + + if (System.currentTimeMillis() - lastRandom < 2000) + return; + source.setTimeStamp("RandomRoll", System.currentTimeMillis()); + + //handle random roll + int max = msg.getMax(); + + if (max > 0) + msg.setRoll(ThreadLocalRandom.current().nextInt(max) + 1); + else if (max < 0) { + max = 1 - max; + msg.setRoll((ThreadLocalRandom.current().nextInt(max) - max) + 1); + } + + msg.setSourceType(source.getObjectType().ordinal()); + msg.setSourceID(source.getObjectUUID()); + + //send to all in range + DispatchMessage.dispatchMsgToInterestArea(source, msg, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, true); + } + + //returns true if looted item is goldItem and is split. Otherwise returns false + + private static void stuck(ClientConnection origin) { + + PlayerCharacter sourcePlayer = origin.getPlayerCharacter(); + + if (sourcePlayer == null) + return; + + if (sourcePlayer.getTimers().containsKey("Stuck")) + return; + + StuckJob sj = new StuckJob(sourcePlayer); + JobContainer jc = JobScheduler.getInstance().scheduleJob(sj, 10000); // Convert + ConcurrentHashMap timers = sourcePlayer.getTimers(); + + if (timers != null) { + if (timers.containsKey("Stuck")) { + timers.get("Stuck").cancelJob(); + timers.remove("Stuck"); + } + timers.put("Stuck", jc); + } + } + + private static void GuildTreeStatusMsg(GuildTreeStatusMsg msg, ClientConnection origin) throws SQLException { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + Dispatch dispatch; + + if (player == null) + return; + + if (origin.guildtreespam > System.currentTimeMillis()) { + return; + } + origin.guildtreespam = System.currentTimeMillis() + 5000; + + Building b = BuildingManager.getBuildingFromCache(msg.getTargetID()); + if (b == null) + return; + + GuildTreeStatusMsg gtsm = new GuildTreeStatusMsg(b, player); + gtsm.configure(); + + dispatch = Dispatch.borrow(player, gtsm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + private static void openSellToNPCWindow(SellToNPCWindowMsg msg, ClientConnection origin) { + + PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin); + Dispatch dispatch; + + if (sourcePlayer == null) + return; + + NPC npc = NPC.getFromCache(msg.getNPCID()); + + if (npc == null) + return; + + // test within talking range + + if (sourcePlayer.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 14); + return; + } + + Contract con = npc.getContract(); + + if (con == null) + return; + float bargain = sourcePlayer.getBargain(); + + float profit = npc.getBuyPercent(sourcePlayer) + bargain; + + if (profit > 1) + profit = 1; + + msg.setupOutput(); + + msg.setUnknown05(profit); + msg.setUnknown06(500000); //TODO set goldItem on npc later + msg.setItemType(con.getBuyItemType()); + msg.setSkillTokens(con.getBuySkillToken()); + msg.setUnknownArray(con.getBuyUnknownToken()); + + dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + private static void sellToNPC(SellToNPCMsg msg, ClientConnection origin) { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + Dispatch dispatch; + + if (player == null) + return; + + CharacterItemManager itemMan = player.getCharItemManager(); + + if (itemMan == null) + return; + + NPC npc = NPC.getFromCache(msg.getNPCID()); + + if (npc == null) + return; + + Item gold = itemMan.getGoldInventory(); + + if (gold == null) + return; + + if (origin.sellLock.tryLock()) { + try { + Item sell; + int cost = 0; + + + if (npc.getCharItemManager().getInventoryCount() > 150) { + if (npc.getParentZone() != null && npc.getParentZone().getPlayerCityUUID() == 0) { + ArrayList inv = npc.getInventory(); + for (int i = 0; i < 20; i++) { + try { + Item toRemove = inv.get(i); + if (toRemove != null) + npc.getCharItemManager().delete(toRemove); + } catch (Exception e) { + break; + } + + } + } + + } + + // Early exit sanity check + + if (msg.getItemType() == GameObjectType.Item.ordinal() == false) + return; + + sell = Item.getFromCache(msg.getItemID()); + + if (sell == null) + return; + + //get item to sell + + ItemBase ib = sell.getItemBase(); + + if (ib == null) + return; + + if (npc.getParentZone() != null && npc.getParentZone().getPlayerCityUUID() != 0) + if (!npc.getCharItemManager().hasRoomInventory(ib.getWeight())) { + + ErrorPopupMsg.sendErrorPopup(player, 21); + return; + } + + if (!sell.validForInventory(origin, player, itemMan)) + return; + + //get goldItem cost to sell + + + cost = sell.getBaseValue(); + + //apply damaged value reduction + float durabilityCurrent = sell.getDurabilityCurrent(); + float durabilityMax = sell.getDurabilityMax(); + float damagedModifier = durabilityCurrent / durabilityMax; + cost *= damagedModifier; + float bargain = player.getBargain(); + + float profit = npc.getBuyPercent(player) + bargain; + + if (profit > 1) + profit = 1; + + + cost *= profit; + + if (gold.getNumOfItems() + cost > 10000000) { + return; + } + + if (gold.getNumOfItems() + cost < 0) + return; + + //TODO make sure npc can buy item type + //test room available for item on npc + + // if (!npc.isStatic() && npc.getCharItemManager().getInventoryCount() > 150) { + // // chatMan.chatSystemInfo(pc, "This vendor's inventory is full"); + // return; + // } + + //make sure item is in player inventory + + Building building = (!npc.isStatic()) ? npc.getBuilding() : null; + + if (building != null && building.getProtectionState().equals(ProtectionState.NPC)) + building = null; + if (npc.getParentZone().getPlayerCityUUID() == 0) + building = null; + + //make sure npc can afford item + + if (building != null && !building.hasFunds(cost)) { + ErrorPopupMsg.sendErrorPopup(player, 17); + return; + } + if (building != null && (building.getStrongboxValue() - cost) < 0) { + ErrorPopupMsg.sendErrorPopup(player, 17); + return; + } + + //TODO transfer item and goldItem transfer should be handled together incase failure + //transfer the item + + if (!itemMan.sellToNPC(sell, npc)) + return; + + if (!itemMan.sellToNPC(building, cost, sell)) + return; + + //handle goldItem transfer + + if (sell == null) + return; + + // ***REFACTOR: SellToNpc sends this message, is this a duplicate? + + //update player's goldItem count + UpdateGoldMsg ugm = new UpdateGoldMsg(player); + ugm.configure(); + + dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + //send the sell message back to update player + msg.setItemType(sell.getObjectType().ordinal()); + msg.setItemID(sell.getObjectUUID()); + msg.setUnknown01(cost); //not sure if this is correct + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } finally { + origin.sellLock.unlock(); + } + } else { + ErrorPopupMsg.sendErrorPopup(player, 12); + } + } + + private static void openBuyFromNPCWindow(BuyFromNPCWindowMsg msg, ClientConnection origin) { + + PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin); + Dispatch dispatch; + + if (sourcePlayer == null) + return; + + NPC npc = NPC.getFromCache(msg.getNpcID()); + + if (npc == null) + return; + + // test within talking range + + if (sourcePlayer.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 14); + return; + } + + dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + private static void buyFromNPC(BuyFromNPCMsg msg, ClientConnection origin) { + + PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin); + + if (sourcePlayer == null) + return; + + if (origin.buyLock.tryLock()) { + + try { + CharacterItemManager itemMan = sourcePlayer.getCharItemManager(); + + if (itemMan == null) + return; + + NPC npc = NPC.getFromCache(msg.getNPCID()); + + if (npc == null) + return; + + Item gold = itemMan.getGoldInventory(); + + if (gold == null) + return; + + Item buy = null; + + if (msg.getItemType() == GameObjectType.MobEquipment.ordinal()) { + ArrayList sellInventory = npc.getContract().getSellInventory(); + if (sellInventory == null) + return; + for (MobEquipment me : sellInventory) { + if (me.getObjectUUID() == msg.getItemID()) { + ItemBase ib = me.getItemBase(); + if (ib == null) + return; + + //test room available for item + if (!itemMan.hasRoomInventory(ib.getWeight())) + return; + + int cost = me.getMagicValue(); + + float bargain = sourcePlayer.getBargain(); + + float profit = npc.getSellPercent(sourcePlayer) - bargain; + + if (profit < 1) + profit = 1; + + + cost *= profit; + + + if (gold.getNumOfItems() - cost < 0) { + //dont' have enough goldItem exit! + // chatMan.chatSystemInfo(pc, "" + "You dont have enough gold."); + return; + } + + Building b = (!npc.isStatic()) ? npc.getBuilding() : null; + + if (b != null && b.getProtectionState().equals(ProtectionState.NPC)) + b = null; + int buildingDeposit = cost - me.getMagicValue(); + if (b != null && (b.getStrongboxValue() + buildingDeposit) > b.getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 206); + return; + } + + if (!itemMan.buyFromNPC(b, cost, buildingDeposit)) { + // chatMan.chatSystemInfo(pc, "" + "You Failed to buy the item."); + return; + } + + buy = Item.createItemForPlayer(sourcePlayer, ib); + + if (buy != null) { + me.transferEnchants(buy); + itemMan.addItemToInventory(buy); + //itemMan.updateInventory(); + } + } + } + } else if (msg.getItemType() == GameObjectType.Item.ordinal()) { + + CharacterItemManager npcCim = npc.getCharItemManager(); + + if (npcCim == null) + return; + + buy = Item.getFromCache(msg.getItemID()); + + if (buy == null) + return; + + ItemBase ib = buy.getItemBase(); + + if (ib == null) + return; + + if (!npcCim.inventoryContains(buy)) + return; + + //test room available for item + if (!itemMan.hasRoomInventory(ib.getWeight())) + return; + + //TODO test cost and subtract goldItem + + //TODO CHnage this if we ever put NPc city npcs in buildings. + int cost = buy.getBaseValue(); + + if (buy.isID() || buy.isCustomValue()) + cost = buy.getMagicValue(); + + float bargain = sourcePlayer.getBargain(); + + float profit = npc.getSellPercent(sourcePlayer) - bargain; + + if (profit < 1) + profit = 1; + + if (!buy.isCustomValue()) + cost *= profit; + else + cost = buy.getValue(); + + + if (gold.getNumOfItems() - cost < 0) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 128); // Insufficient Gold + return; + } + + Building b = (!npc.isStatic()) ? npc.getBuilding() : null; + + if (b != null) + if (b.getProtectionState().equals(ProtectionState.NPC)) + b = null; + + int buildingDeposit = cost; + + if (b != null && (b.getStrongboxValue() + buildingDeposit) > b.getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 206); + return; + } + + if (!itemMan.buyFromNPC(b, cost, buildingDeposit)) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 110); + return; + } + + if (buy != null) + itemMan.buyFromNPC(buy, npc); + + } else if (msg.getItemType() == GameObjectType.MobLoot.ordinal()) { + + CharacterItemManager npcCim = npc.getCharItemManager(); + + if (npcCim == null) + return; + + buy = MobLoot.getFromCache(msg.getItemID()); + + if (buy == null) + return; + + ItemBase ib = buy.getItemBase(); + + if (ib == null) + return; + + if (!npcCim.inventoryContains(buy)) + return; + + //test room available for item + if (!itemMan.hasRoomInventory(ib.getWeight())) + return; + + //TODO test cost and subtract goldItem + + //TODO CHnage this if we ever put NPc city npcs in buildings. + + int cost = buy.getMagicValue(); + cost *= npc.getSellPercent(sourcePlayer); + + + if (gold.getNumOfItems() - cost < 0) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 128); // Insufficient Gold + return; + } + + Building b = (!npc.isStatic()) ? npc.getBuilding() : null; + + if (b != null && b.getProtectionState().equals(ProtectionState.NPC)) + b = null; + int buildingDeposit = cost; + + if (b != null && (b.getStrongboxValue() + buildingDeposit) > b.getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 206); + return; + } + + if (!itemMan.buyFromNPC(b, cost, buildingDeposit)) + return; + + if (buy != null) + itemMan.buyFromNPC(buy, npc); + + } else + return; + + if (buy != null) { + + msg.setItem(buy); + //send the buy message back to update player + // msg.setItemType(buy.getObjectType().ordinal()); + // msg.setItemID(buy.getObjectUUID()); + Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + itemMan.updateInventory(); + } + + } finally { + origin.buyLock.unlock(); + } + } else { + ErrorPopupMsg.sendErrorPopup(origin.getPlayerCharacter(), 12); // All production slots taken + } + + } + + private static void Repair(RepairMsg msg, ClientConnection origin) { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + Dispatch dispatch; + + if (player == null) + return; + + NPC npc = NPC.getFromCache(msg.getNPCID()); + + if (npc == null) + return; + + if (msg.getMsgType() == 1) { //Open RepairObject Window + + if (player.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { + ErrorPopupMsg.sendErrorPopup(player, 14); + return; + } + + //send open repair window response + msg.setRepairWindowAck(npc); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } else if (msg.getMsgType() == 0) { //Request RepairObject + + CharacterItemManager itemMan = player.getCharItemManager(); + + if (itemMan == null) + return; + + Item gold = itemMan.getGoldInventory(); + + if (gold == null) + return; + + Item toRepair = Item.getFromCache(msg.getItemID()); + + if (toRepair == null) + return; + + if (toRepair.getItemBase().isGlass()) + return; + + //make sure item is in player's inventory or equipment + if (!itemMan.inventoryContains(toRepair) && !itemMan.equippedContains(toRepair)) + return; + + //make sure item is damaged and not destroyed + short dur = toRepair.getDurabilityCurrent(); + short max = toRepair.getDurabilityMax(); + //account for durability modifications + float durMod = toRepair.getBonusPercent(ModType.Durability, SourceType.None); + max *= (1 + (durMod * 0.01f)); + if (dur >= max || dur < 1) { + //redundancy message to clear item from window in client + toRepair.setDurabilityCurrent(max); + msg.setupRepairAck(max - dur); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return; + } + //TODO get cost to repair + int cost = (int) ((max - dur) * 80.1); + Building b = (!npc.isStatic()) ? npc.getBuilding() : null; + + if (b != null) + if (b.getProtectionState().equals(ProtectionState.NPC)) + b = null; + + + if (b != null && (b.getStrongboxValue() + cost) > b.getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(player, 206); + return; + } + + if (player.getCharItemManager().getGoldInventory().getNumOfItems() - cost < 0) + return; + + if (player.getCharItemManager().getGoldInventory().getNumOfItems() - cost > MBServerStatics.PLAYER_GOLD_LIMIT) + return; + + if (!itemMan.buyFromNPC(b, cost, cost)) { + ErrorPopupMsg.sendErrorPopup(player, 128); + return; + } + + //update player's goldItem count + UpdateGoldMsg ugm = new UpdateGoldMsg(player); + ugm.configure(); + dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + //update durability to database + if (!DbManager.ItemQueries.SET_DURABILITY(toRepair, max)) + return; + //repair the item + toRepair.setDurabilityCurrent(max); + //send repair msg + msg.setupRepairAck(max - dur); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + } + + protected static void petAttack(PetAttackMsg msg, ClientConnection conn) throws MsgSendException { + + PlayerCharacter pc = SessionManager.getPlayerCharacter(conn); + + if (pc == null) + return; + + Mob pet = pc.getPet(); + + if (pet == null) + return; + + if (!pet.isAlive()) + return; + + if ((pc.inSafeZone()) + && (msg.getTargetType() == GameObjectType.PlayerCharacter.ordinal())) + return; + + CombatManager.setAttackTarget(msg, conn); + if (pet.getCombatTarget() == null) + return; + } + + protected static void petCmd(PetCmdMsg msg, ClientConnection conn) throws MsgSendException { + + PlayerCharacter pc = SessionManager.getPlayerCharacter(conn); + + if (pc == null) + return; + + Mob pet = pc.getPet(); + + if (pet == null) + return; + + if (!pet.isAlive()) + return; + + //if (pet.state == STATE.Disabled) + // return; + + int type = msg.getType(); + + if (type == 1) { //stop attack + pet.setCombatTarget(null); + pc.setCombat(false); + + } else if (type == 2) { //dismiss + pet.dismiss(); + pc.dismissPet(); + + if (pet.isAlive()) + WorldGrid.updateObject(pet); + } else if (type == 3) //toggle assist + pet.toggleAssist(); + else if (type == 5) { //rest + boolean sit = (!(pet.isSit())); + pet.setSit(sit); + + // cancel effects that break on sit + if (pet.isSit()) + pet.cancelOnSit(); + + UpdateStateMsg rwss = new UpdateStateMsg(); + rwss.setPlayer(pet); + DispatchMessage.sendToAllInRange(pet, rwss); + } + } + + //Handle RepairObject Window and RepairObject Requests + + protected static void HandlePromptRecall(PromptRecallMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + boolean recallAccepted; + + if (player == null) + return; + + boolean confirmed = msg.getConfirmed(); + + if (confirmed == true) { + long timeElapsed = System.currentTimeMillis() - player.getTimeStamp("PromptRecall"); + //send fail message + recallAccepted = timeElapsed < 15000; + } else + recallAccepted = false; + + if (recallAccepted == true) { + //handle recall + long type = player.getTimeStamp("LastRecallType"); + + if (type == 1) { //recall to bind + player.teleport(player.getBindLoc()); + player.setSafeMode(); + } else { //recall to rg + float dist = 9999999999f; + Building rg = null; + Vector3fImmutable rgLoc; + + for (Runegate runegate : Runegate._runegates.values()) { + + rgLoc = runegate.gateBuilding.getLoc(); + + float distanceSquaredToRunegate = player.getLoc().distanceSquared2D(rgLoc); + + if (distanceSquaredToRunegate < sqr(dist)) + rg = runegate.gateBuilding; + + } + + //nearest runegate found. teleport characterTarget + + if (rg != null) { + player.teleport(rg.getLoc()); + player.setSafeMode(); + } + } + } + } + + @Override + public boolean handleClientMsg(ClientNetMsg msg) { + + if (msg == null) { + Logger.error("handleClientMsg", "Recieved null msg. Returning."); + return false; + } + + ClientConnection origin; + Protocol protocolMsg = Protocol.NONE; + Session s; + + try { + + // Try registered opcodes first as we take a hatchet to this GodObject + + AbstractClientMsgHandler msgHandler = msg.getProtocolMsg().handler; + + if (msgHandler != null) + return msgHandler.handleNetMsg(msg); + + // Any remaining opcodes fall through and are routed + // through this ungodly switch of doom. + + origin = (ClientConnection) msg.getOrigin(); + s = SessionManager.getSession(origin); + + protocolMsg = msg.getProtocolMsg(); + + switch (protocolMsg) { + case SETSELECTEDOBECT: + ClientMessagePump.targetObject((TargetObjectMsg) msg, origin); + break; + + /* + * Chat + */ + + // Simplify by fall through. Route in ChatManager + case CHATSAY: + case CHATSHOUT: + case CHATTELL: + case CHATGUILD: + case CHATGROUP: + case CHATPVP: + case CHATIC: + case CHATCITY: + case CHATINFO: + case SYSTEMBROADCASTCHANNEL: + case CHATCSR: + case SYSTEMCHANNEL: + case GLOBALCHANNELMESSAGE: + case LEADERCHANNELMESSAGE: + ChatManager.handleChatMsg(s, (AbstractChatMsg) msg); + break; + case UPDATESTATE: + UpdateStateMsg rwss = (UpdateStateMsg) msg; + runWalkSitStand(rwss, origin); + break; + case ACTIVATECHARTER: + UseCharterMsg ucm = (UseCharterMsg) msg; + ucm.setUnknown02(1); + ucm.configure(); + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), ucm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + break; + case CHECKUNIQUEGUILD: + break; + case CANCELGUILDCREATION: + break; + case LEAVEREQUEST: + origin.disconnect(); + break; + case POWER: + PowersManager.usePower((PerformActionMsg) msg, origin, false); + break; + case REQUESTMELEEATTACK: + CombatManager.setAttackTarget((AttackCmdMsg) msg, origin); + break; + case READYTOENTER: + break; + case OPENVAULT: + break; + case WHOREQUEST: + WhoRequest((WhoRequestMsg) msg, origin); + break; + case CLIENTADMINCOMMAND: + ChatManager.HandleClientAdminCmd((ClientAdminCommandMsg) msg, origin); + break; + case SOCIALCHANNEL: + social((SocialMsg) msg, origin); + break; + case COMBATMODE: + CombatManager.toggleCombat((ToggleCombatMsg) msg, origin); + break; + case ARCCOMBATMODEATTACKING: + CombatManager.toggleCombat((SetCombatModeMsg) msg, origin); + break; + case MODIFYGUILDSTATE: + ToggleLfgRecruitingMsg tlrm = (ToggleLfgRecruitingMsg) msg; + toggleLfgRecruiting(tlrm, origin); + break; + case TOGGLESITSTAND: + ToggleSitStandMsg tssm = (ToggleSitStandMsg) msg; + toggleSitStand(tssm, origin); + break; + case GUILDTREESTATUS: + GuildTreeStatusMsg((GuildTreeStatusMsg) msg, origin); + break; + case IGNORE: + ((IgnoreMsg) msg).handleRequest(origin); + break; + case UNEQUIP: + TransferItemFromEquipToInventory((TransferItemFromEquipToInventoryMsg) msg, origin); + break; + case EQUIP: + TransferItemFromInventoryToEquip((TransferItemFromInventoryToEquipMsg) msg, origin); + break; + case DELETEOBJECT: + DeleteItem((DeleteItemMsg) msg, origin); + break; + case VIEWRESOURCES: + ViewResourcesMessage((ViewResourcesMessage) msg, origin); + break; + case RAISEATTR: + modifyStat((ModifyStatMsg) msg, origin); + break; + case COSTTOOPENBANK: + ackBankWindowOpened((AckBankWindowOpenedMsg) msg, origin); + break; + case RESETAFTERDEATH: + respawn((RespawnMsg) msg, origin); + break; + case REQUESTCONTENTS: + lootWindowRequest((LootWindowRequestMsg) msg, origin); + break; + case MOVEOBJECTTOCONTAINER: + loot((LootMsg) msg, origin); + break; + case SHOWCOMBATINFO: + show((ShowMsg) msg, origin); + break; + case TRANSFERITEMTOBANK: + transferItemFromInventoryToBank((TransferItemFromInventoryToBankMsg) msg, origin); + break; + case TRANSFERITEMFROMBANK: + transferItemFromBankToInventory((TransferItemFromBankToInventoryMsg) msg, origin); + break; + case TRANSFERITEMFROMVAULTTOINVENTORY: + transferItemFromVaultToInventory((TransferItemFromVaultToInventoryMsg) msg, origin); + break; + case ITEMTOVAULT: + transferItemFromInventoryToVault((TransferItemFromInventoryToVaultMsg) msg, origin); + break; + case TRANSFERGOLDFROMVAULTTOINVENTORY: + transferGoldFromVaultToInventory((TransferGoldFromVaultToInventoryMsg) msg, origin); + break; + case GOLDTOVAULT: + transferGoldFromInventoryToVault((TransferGoldFromInventoryToVaultMsg) msg, origin); + break; + case REQUESTTOTRADE: + TradeManager.tradeRequest((TradeRequestMsg) msg, origin); + break; + case REQUESTTRADEOK: + TradeManager.acceptTradeRequest((AcceptTradeRequestMsg) msg, origin); + break; + case REQUESTTRADECANCEL: + TradeManager.rejectTradeRequest((RejectTradeRequestMsg) msg, origin); + break; + case TRADEADDOBJECT: + TradeManager.addItemToTradeWindow((AddItemToTradeWindowMsg) msg, origin); + break; + case TRADEADDGOLD: + TradeManager.addGoldToTradeWindow((AddGoldToTradeWindowMsg) msg, origin); + break; + case TRADECONFIRM: + TradeManager.commitToTrade((CommitToTradeMsg) msg, origin); + break; + case TRADEUNCONFIRM: + TradeManager.uncommitToTrade((UncommitToTradeMsg) msg, origin); + break; + case TRADECLOSE: + TradeManager.closeTradeWindow((CloseTradeWindowMsg) msg, origin); + break; + case ARCREQUESTTRADEBUSY: + TradeManager.invalidTradeRequest((InvalidTradeRequestMsg) msg); + break; + case VENDORDIALOG: + VendorDialogMsg.replyDialog((VendorDialogMsg) msg, origin); + break; + case SHOPLIST: + openBuyFromNPCWindow((BuyFromNPCWindowMsg) msg, origin); + break; + case BUYFROMNPC: + buyFromNPC((BuyFromNPCMsg) msg, origin); + break; + case SHOPINFO: + openSellToNPCWindow((SellToNPCWindowMsg) msg, origin); + break; + case SELLOBJECT: + sellToNPC((SellToNPCMsg) msg, origin); + break; + case REPAIROBJECT: + Repair((RepairMsg) msg, origin); + break; + case TRAINERLIST: + WorldServer.trainerInfo((TrainerInfoMsg) msg, origin); + break; + case ARCUNTRAINLIST: + WorldServer.refinerScreen((RefinerScreenMsg) msg, origin); + break; + case TRAINSKILL: + TrainMsg.train((TrainMsg) msg, origin); + break; + case ARCUNTRAINABILITY: + RefineMsg.refine((RefineMsg) msg, origin); + break; + case POWERTARGNAME: + PowersManager.summon((SendSummonsRequestMsg) msg, origin); + break; + case ARCSUMMON: + PowersManager.recvSummon((RecvSummonsRequestMsg) msg, origin); + break; + case ARCTRACKINGLIST: + PowersManager.trackWindow((TrackWindowMsg) msg, origin); + break; + case STUCK: + stuck(origin); + break; + case RANDOM: + ClientMessagePump.randomRoll((RandomMsg) msg, origin); + break; + case ARCPETATTACK: + petAttack((PetAttackMsg) msg, origin); + break; + case ARCPETCMD: + petCmd((PetCmdMsg) msg, origin); + break; + case MANAGENPC: + ManageNPCCmd((ManageNPCMsg) msg, origin); + break; + case ARCPROMPTRECALL: + HandlePromptRecall((PromptRecallMsg) msg, origin); + break; + case CHANNELMUTE: + break; + case KEEPALIVESERVERCLIENT: + break; + case UNKNOWN: + break; + + case CONFIRMPROMOTE: + break; + + default: + String ocHex = StringUtils.toHexString(protocolMsg.opcode); + Logger.error("Cannot handle Opcode: " + ocHex + " " + protocolMsg.name()); + return false; + + } + + } catch (MsgSendException | SQLException e) { + Logger.error("handler for " + protocolMsg + " failed: " + e); + return false; + } + + return true; + } + + //TODO what is this used for? + private void ManageNPCCmd(ManageNPCMsg msg, ClientConnection origin) { + + } + + //call this if the transfer fails server side to kick the item back to inventory from equip + private void forceTransferFromInventoryToEquip(TransferItemFromEquipToInventoryMsg msg, ClientConnection origin, String reason) { + //TODO add this later + //PATCHED CODEZZ + } + +} diff --git a/src/engine/net/client/Protocol.java b/src/engine/net/client/Protocol.java new file mode 100644 index 00000000..87960fa9 --- /dev/null +++ b/src/engine/net/client/Protocol.java @@ -0,0 +1,337 @@ +package engine.net.client; + +/* This class defines Magicbane's application network protocol. +--> Name / Opcode / Message / Handler + */ + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.client.handlers.*; +import engine.net.client.msg.*; +import engine.net.client.msg.chat.*; +import engine.net.client.msg.commands.ClientAdminCommandMsg; +import engine.net.client.msg.group.*; +import engine.net.client.msg.guild.*; +import engine.net.client.msg.login.*; +import org.pmw.tinylog.Logger; + +import java.lang.reflect.Constructor; +import java.util.HashMap; + +public enum Protocol { + + + NONE(0x0, null, null), + ABANDONASSET(0xFDDBB233, AbandonAssetMsg.class, AbandonAssetMsgHandler.class), // AbandonAsset + ACTIVATECHARTER(0x296C0B22, UseCharterMsg.class, null),// Use Guild Charter + ACTIVATENPC(0xC9AAE81E, ActivateNPCMessage.class, ActivateNPCMsgHandler.class), + ACTIVATEPLEDGE(0x5A694DC0, SwearInMsg.class, SwearInHandler.class), // Swear In + ADDFRIEND(0xCFA1C787, AddFriendMessage.class, null), + ALLIANCECHANGE(0x0E7D0B57, AllianceChangeMsg.class, AllianceChangeMsgHandler.class), // Remove From Allies/Enemies List + ALLYENEMYLIST(0xAEA443FD, AllyEnemyListMsg.class, AllyEnemyListMsgHandler.class), + ARCCOMBATMODEATTACKING(0xD8B10579, SetCombatModeMsg.class, null), // Attack From Outside Combat Mode + ARCHOTZONECHANGE(0xDCFF196F, null, null), //change hotzone + ARCIGNORELISTUPDATE(0x4B1B17C2, IgnoreListMsg.class, null), //req/show ignore list + ARCLOGINNOTIFY(0x010FED87, ArcLoginNotifyMsg.class, ArcLoginNotifyMsgHandler.class), //Client Confirms entering world + ARCMINECHANGEPRODUCTION(0x1EAA993F, ArcMineChangeProductionMsg.class, ArcMineChangeProductionMsgHandler.class), + ARCMINETOWERCRESTUPDATE(0x34164D0D, null, null), + ARCMINEWINDOWAVAILABLETIME(0x6C909DE7, ArcMineWindowAvailableTimeMsg.class, ArcMineWindowAvailableTimeHandler.class), + ARCMINEWINDOWCHANGE(0x92B2148A, ArcMineWindowChangeMsg.class, MineWindowChangeHandler.class), + ARCOWNEDMINESLIST(0x59184455, ArcOwnedMinesListMsg.class, ArcOwnedMinesListHandler.class), + ARCPETATTACK(0x18CD61AD, PetAttackMsg.class, null), // Pet Attack + ARCPETCMD(0x4E80E001, PetCmdMsg.class, null), // Stop ArcPetAttack, Toggle Assist, Toggle Rest + ARCPOWERPROJECTILE(0xA2312D3B, null, null), + ARCPROMPTRECALL(0xE3196B6E, PromptRecallMsg.class, null), //Recall Prompt + ARCREQUESTTRADEBUSY(0xD4BAB4DF, InvalidTradeRequestMsg.class, null), // Attempt trade with someone who is already trading + ARCSERVERSTATUS(0x87BA4462, null, null), //Update Server Status + ARCSIEGESPIRE(0x36A49BC6, ArcSiegeSpireMsg.class, ArcSiegeSpireMsgHandler.class), // Activate/Deactivate Spires + ARCSUMMON(0xFD816A0A, RecvSummonsRequestMsg.class, null), // Suspect Recv Summons Request + ARCTRACKINGLIST(0xC89CF08B, TrackWindowMsg.class, null), //Request/Send Track window + ARCTRACKOBJECT(0x609B6BA2, TrackArrowMsg.class, null), //Send Track Arrow + ARCUNTRAINABILITY(0x548DBF83, RefineMsg.class, null), //Refine + ARCUNTRAINLIST(0x38879E90, RefinerScreenMsg.class, null), //Refiner screen + ARCVIEWASSETTRANSACTIONS(0xBFA476E4, ArcViewAssetTransactionsMsg.class, ArcViewAssetTransactionsMsgHandler.class), + ASSETSUPPORT(0xc481f89D, AssetSupportMsg.class, AssetSupportMsgHandler.class), + BANISHMEMBER(0x31AA3368, BanishUnbanishMsg.class, BanishUnbanishHandler.class), // Banish/Unbanish + BANKINVENTORY(0x32F3F503, ShowBankInventoryMsg.class, null), // ShowCombatInfo Bank Inventory + BREAKFEALTY(0x479A4C19, BreakFealtyMsg.class, BreakFealtyHandler.class), + BUYFROMNPC(0xA2B8DFA5, BuyFromNPCMsg.class, null), // Buy Item From NPC + CANCELGUILDCREATION(0x385EA922, GuildCreationCloseMsg.class, GuildCreationCloseHandler.class), //Close the window + CHANGEALTITUDE(0x624F08BA, ChangeAltitudeMsg.class, ChangeAltitudeHandler.class), //Change Altitude + CHANGEGUILDLEADER(0xE40BC95D, ChangeGuildLeaderMsg.class, ChangeGuildLeaderHandler.class), + CHANNELMUTE(0xC1BDC53A, ChatFilterMsg.class, ChannelMuteMsgHandler.class), //Chat Channels that are turned on + CHARSELECTSCREEN(0x682C935D, null, null), // Character Selection Screen + CHATCITY(0x9D402901, ChatCityMsg.class, null), // Chat Channel: /City + CHATCSR(0x14EBA1C3, ChatCSRMsg.class, null), //Chat Channel: CSR + CHATGROUP(0xA895B634, ChatGroupMsg.class, null), // Chat Channel: /group + CHATGUILD(0xA9D92ED4, ChatGuildMsg.class, null), // Chat Channel: /guild + CHATIC(0x00A75F35, ChatICMsg.class, null), // Chat Channel: /IC + CHATINFO(0x9D4B61EB, ChatInfoMsg.class, null), // Chat Channel: /Info + CHATPVP(0x14EBA570, ChatPvPMsg.class, null), // Chat Channel: PVP + CHATSAY(0x14EA0393, ChatSayMsg.class, null), // Chat Channel: /say + CHATSHOUT(0xA8D5B560, ChatShoutMsg.class, null), // Chat Channel: /shout + CHATTELL(0x9D4AC896, ChatTellMsg.class, null), // Chat Channel: /tell + CHECKUNIQUEGUILD(0x689097D7, GuildCreationOptionsMsg.class, GuildCreationOptionsHandler.class), // Set Guild Name/Motto in Use Guild Charter + CITYASSET(0x7cae1678, CityAssetMsg.class, null), + CITYCHOICE(0x406610BB, CityChoiceMsg.class, CityChoiceMsgHandler.class), + CITYDATA(0xB8A947D4, CityDataMsg.class, CityDataHandler.class), + CITYZONE(0x254947F2, CityZoneMsg.class, null), //For Creating City Object Clientside(Terraform)/Rename City. + CLAIMASSET(0x948C62CC, ClaimAssetMsg.class, ClaimAssetMsgHandler.class), // ClaimAsset + CLAIMGUILDTREE(0xFD1C6442, ClaimGuildTreeMsg.class, ClaimGuildTreeMsgHandler.class), + CLIENTADMINCOMMAND(0x624EAB5F, ClientAdminCommandMsg.class, null), //Admin Command + CLIENTUPDATEVAULT(0x66EDBECD, UpdateVaultMsg.class, null), + COMBATMODE(0xFE4BF353, ToggleCombatMsg.class, null), //Toggle Combat mode + CONFIRMPROMOTE(0x153BB5F9, ConfirmPromoteMsg.class, null), + COSTTOOPENBANK(0x135BE5E8, AckBankWindowOpenedMsg.class, null), // ACK Bank Window Opened + CREATECHAR(0x5D18B5C8, CommitNewCharacterMsg.class, null), // Commit New Character, + CREATEPETITION(0xD489CFED, GuildCreationFinalizeMsg.class, GuildCreationFinalizeHandler.class), //Confirm guild creation + CUSTOMERPETITION(0x7F9D7D6D, PetitionReceivedMsg.class, PetitionReceivedMsgHandler.class), + DELETEOBJECT(0x57F069D8, DeleteItemMsg.class, null), //Delete Item from Inventory + DESTROYBUILDING(0x3CB6FAD3, DestroyBuildingMsg.class, DestroyBuildingHandler.class), // Destroy Building + DISBANDGUILD(0x77AABD64, DisbandGuildMsg.class, DisbandGuildHandler.class), //Disband Guild + DISMISSGUILD(0x8D2D3D61, DismissGuildMsg.class, DismissGuildHandler.class), + DOORTRYOPEN(0xA83DD8C8, DoorTryOpenMsg.class, DoorTryOpenMsgHandler.class), // Open/Close Door + ENTERWORLD(0xB9783F85, RequestEnterWorldMsg.class, RequestEnterWorldHandler.class), // Request Enter World + EQUIP(0x3CB1AF8C, TransferItemFromInventoryToEquipMsg.class, null), // Transfer Item from Inventory to Equip + EXPERIENCE(0xC57802A7, GrantExperienceMsg.class, null), //TODO rename once identified + FORGETOBJECTS(0xE307A0E1, UnloadObjectsMsg.class, null), // Unload Objects + FRIENDACCEPT(0xCA297870, AcceptFriendMsg.class, FriendAcceptHandler.class), + FRIENDDECLINE(0xF08FC279, DeclineFriendMsg.class, FriendDeclineHandler.class), + FURNITURE(0xCE7FA503, FurnitureMsg.class, FurnitureHandler.class), + GAMESERVERIPRESPONSE(0x6C95CF87, GameServerIPResponseMsg.class, null), // Game Server IP Response + GLOBALCHANNELMESSAGE(0x2bf03fd2, null, null), + GOLDTOVAULT(0x3ABAEE49, TransferGoldFromInventoryToVaultMsg.class, null), // Transfer Gold from Inventory to Vault + GROUPDISBAND(0xE2B85AA4, DisbandGroupMsg.class, DisbandGroupHandler.class), //Disband Group + GROUPFOLLOW(0xC61B0476, FormationFollowMsg.class, FormationFollowHandler.class), //Toggle Follow, set Formation + GROUPLEADERAPPOINT(0xEF778DD3, AppointGroupLeaderMsg.class, AppointGroupLeaderHandler.class), //Appoint new group leader + GROUPREMOVE(0x6E50277C, RemoveFromGroupMsg.class, RemoveFromGroupHandler.class), //Remove from Group + GROUPTREASURE(0x01041C66, ToggleGroupSplitMsg.class, ToggleGroupSplitHandler.class), // Toggle Group Split + GUILDMEMBERONLINE(0x7B79EB3A, GuildEnterWorldMsg.class, null), // Send Enter World Message to Guild + GUILDRANKCHANGE(0x0DEFB21F, ChangeRankMsg.class, ChangeRankHandler.class), // Change Rank + GUILDTREESTATUS(0x4B95FB85, GuildTreeStatusMsg.class, null), + HIRELINGSERVICE(0xD3D93322, HirelingServiceMsg.class, HirelingServiceMsgHandler.class), + IGNORE(0xBD8881EE, IgnoreMsg.class, null), //client sent /ignore command + INITIATETRADEHUDS(0x667D29D8, OpenTradeWindowMsg.class, null), // Open Trade Window + INVITEGROUP(0x004A2012, GroupInviteMsg.class, GroupInviteHandler.class), // Send/Receive/Deny Group Invite + INVITEGUILDFEALTY(0x0274D612, InviteToSubMsg.class, InviteToSubHandler.class), // Invite Guild to Swear + INVITETOGUILD(0x6819062A, InviteToGuildMsg.class, InviteToGuildHandler.class), // Invite player to guild, refuse guild invite + ITEMHEALTHUPDATE(0xB635F55E, ItemHealthUpdateMsg.class, null), //Update Durability of item + ITEMPRODUCTION(0x3CCE8E30, ItemProductionMsg.class, ItemProductionMsgHandler.class), + ITEMTOVAULT(0x3ABE4927, TransferItemFromInventoryToVaultMsg.class, null), // Transfer Item to Vault + JOINFORPROVINCE(0x1FB369CD, AcceptSubInviteMsg.class, AcceptSubInviteHandler.class), //Response to invite to swear? + JOINFORSWORN(0xF6A4170F, null, null), + JOINGROUP(0x7EC5E636, GroupInviteResponseMsg.class, GroupInviteResponseHandler.class), // Accept Group Invite + JOINGUILD(0xF0C5F2FF, AcceptInviteToGuildMsg.class, AcceptInviteToGuildHandler.class), // Accept guild invite + KEEPALIVESERVERCLIENT(0x49EE129C, KeepAliveServerClientMsg.class, KeepAliveServerClientHandler.class), // Keep Alive + LEADERBOARD(0x6F0C1386, LeaderboardMessage.class, null), + LEADERCHANNELMESSAGE(0x17b306f9, ChatGlobalMsg.class, null), + LEAVEGROUP(0xD8037303, LeaveGroupMsg.class, LeaveGroupHandler.class), //Leave Group + LEAVEGUILD(0x1801EA32, LeaveGuildMsg.class, LeaveGuildHandler.class), // Leave Guild + LEAVEREQUEST(0xC79D775C, LeaveWorldMsg.class, null), //Client Request Leave World + LEAVEWORLD(0xB801EAEC, null, null), //Response to client for Request Leave World + LOADCHARACTER(0x5756BC53, null, null), // Load Player/NPC/Mob, other then self + LOADSTRUCTURE(0xB8A3A654, LoadStructureMsg.class, null), //Load Buildings and World Detail Objects + LOCKUNLOCKDOOR(0x8D0E8C44, LockUnlockDoorMsg.class, LockUnlockDoorMsgHandler.class), // Lock/Unlock Door + LOGIN(0x3D51E445, ClientLoginInfoMsg.class, null), // Login Information + LOGINFAILED(0x47B867F6, null, null), // Login Error + LOGINTOGAMESERVER(0x77910FDF, LoginToGameServerMsg.class, LoginToGameServerMsgHandler.class), // Login to Game Server + MANAGECITYASSETS(0xCFF01225, ManageCityAssetsMsg.class, ManageCityAssetMsgHandler.class), // Manage city assets + MANAGENPC(0x43A273FA, null, null), // Open Hireling Management Page + MERCHANT(0x3E645EF4, MerchantMsg.class, MerchantMsgHandler.class), // Open Teleport List, Teleport, Open Shrine, Request Boon, open/manage warehouse window + MINIONTRAINING(0xD355F528, MinionTrainingMessage.class, MinionTrainingMsgHandler.class), + MODIFYGUILDSTATE(0x38936FEA, ToggleLfgRecruitingMsg.class, null), //Toggle LFGroup/LFGuild/Recruiting + MOTD(0xEC841E8D, MOTDMsg.class, MOTDEditHandler.class), //Send/Rec Guild/Nation/IC MOTD Message + MOVECORRECTION(0x47FAD1E3, null, null), //Force move to point? + MOVEOBJECTTOCONTAINER(0xD1639F7C, LootMsg.class, null), //Send/Recv MoveObjectToContainer Msg + MOVETOPOINT(0x49EF7241, MoveToPointMsg.class, MoveToPointHandler.class), // Move to point + NAMEVERIFY(0x1B3BF0B1, null, null), // Invalid Name in Character Creation + NEWWORLD(0x982E4A77, WorldDataMsg.class, null), // World Data + OBJECTACTION(0x06855A36, ObjectActionMsg.class, ObjectActionMsgHandler.class), //Use item + OKCOSTTOOPENBANK(0x6F97A502, null, null), + OPENFRIENDSCONDEMNLIST(0x49E5FE4F, OpenFriendsCondemnListMsg.class, OpenFriendsCondemnListMsgHandler.class), // Friends/Con demn/Kill/Death/Heraldry List + OPENVAULT(0xBE048E50, OpenVaultMsg.class, null), // Open Vault Window + ORDERNPC(0x61C707B1, OrderNPCMsg.class, OrderNPCMsgHandler.class), + PASSIVEMESSAGETRIGGER(0x2FF9E2E4, null, null), //PassiveMessageTriggerMsg + PET(0x624F3D8C, PetMsg.class, null), //Summon Pet? + PLACEASSET(0x940962DF, PlaceAssetMsg.class, PlaceAssetMsgHandler.class), + PLAYERDATA(0xB206D352, SendOwnPlayerMsg.class, null), //Enter World, Own Player Data + PLAYERFRIENDS(0xDDEF9E7D, FriendRequestMsg.class, FriendRequestHandler.class), + POWER(0x3C97A459, PerformActionMsg.class, null), // REQ / CMD Perform Action + POWERACTION(0xA0B27EEB, ApplyEffectMsg.class, null), // Apply Effect, add to effects icons + POWERACTIONDD(0xD43052F8, ModifyHealthMsg.class, null), //Modify Health/Mana/Stamina using power + POWERACTIONDDDIE(0xC27D446B, null, null), //Modify Health/Mana/Stamina using power and kill target + POWERTARGNAME(0x5A807CCE, SendSummonsRequestMsg.class, null), // Send Summons Request + RAISEATTR(0x5EEB65E0, ModifyStatMsg.class, null), // Modify Stat + RANDOM(0xAC5D0135, RandomMsg.class, null), //RequestSend random roll + READYTOENTER(0x490E4FE0, EnterWorldReceivedMsg.class, null), //Client Ack Receive Enter World + REALMDATA(0x2399B775, null, null), //Realm Data - Optional(?) + RECOMMENDNATION(0x6D4579E9, RecommendNationMsg.class, RecommendNationMsgHandler.class), // Recommend as Ally/Enemy, error + RECYCLEPOWER(0x24033B67, RecyclePowerMsg.class, null), //Unlock power for reUse + REMOVECHAR(0x5D3F9739, DeleteCharacterMsg.class, null), // Delete Character + REMOVEFRIEND(0xE0D5DB42, RemoveFriendMessage.class, RemoveFriendHandler.class), + REPAIRBUILDING(0xAF8C2560, RepairBuildingMsg.class, RepairBuildingMsgHandler.class), + REPAIROBJECT(0x782219CE, RepairMsg.class, null), //Repair Window Req/Ack, RepairObject item Req/Ack + REQUESTCONTENTS(0xA786B0A2, LootWindowRequestMsg.class, null), // MoveObjectToContainer Window Request + REQUESTGUILDLIST(0x85DCC6D7, ReqGuildListMsg.class, RequestGuildListHandler.class), + REQUESTMELEEATTACK(0x98C71545, AttackCmdMsg.class, null), // Attack + REQUESTMEMBERLIST(0x3235E5EA, GuildControlMsg.class, GuildControlHandler.class), // Part of Promote/Demote, Also Player History + REQUESTTOOPENBANK(0xF26E453F, null, null), // RequestToOpenBankMsg + REQUESTTOTRADE(0x4D84259B, TradeRequestMsg.class, null), // Trade Request + REQUESTTRADECANCEL(0xCB0C5735, RejectTradeRequestMsg.class, null), // Reject RequestToTrade + REQUESTTRADEOK(0xFFD29841, AcceptTradeRequestMsg.class, null), // Accept Trade Request + RESETAFTERDEATH(0xFDCBB98F, RespawnMsg.class, null), //Respawn Request/Response + ROTATEMSG(0x57F2088E, RotateObjectMsg.class, null), + SAFEMODE(0x9CF3922A, SafeModeMsg.class, null), //Tell client they're in safe mode + SCALEOBJECT(0xE2B392D9, null, null), // Adjust scale of object + SELECTCHAR(0x7E6A9338, GameServerIPRequestMsg.class, null), // Game Server IP Request + SELECTCITY(0x7E6BE630, null, null), + SELECTSERVER(0x440D28B7, ServerInfoMsg.class, null), // Server Info Request/Response + SELLOBJECT(0x57111C67, SellToNPCMsg.class, null), //Sell to NPC + SENDCITYENTRY(0xBC3B5E72, null, null), //Send Teleport/Repledge List + SENDGUILDENTRY(0x6D5EF164, null, null), + SENDMEMBERENTRY(0x6949C720, GuildListMsg.class, GuildListHandler.class), // ShowCombatInfo guild members list, I think + SETITEMFLAG(0xE8C1B53B, null, null), + SETMOTD(0xFD21FC7C, MOTDCommitMsg.class, MOTDCommitHandler.class), //Commit Guild/Nation/IC MOTD Message + SETOBJVAL(0x08A50FD1, null, null), + SETRUNE(0x888E7C64, ApplyRuneMsg.class, null), //Apply Promotion, Stat Rune (maybe disc also) + SETSELECTEDOBECT(0x64E10938, TargetObjectMsg.class, null), // Target an object + SHOPINFO(0x267DAB90, SellToNPCWindowMsg.class, null), //open Sell to NPC Window + SHOPLIST(0x682DAB4D, BuyFromNPCWindowMsg.class, null), // Open Buy From NPC Window + SHOWCOMBATINFO(0x9BF1E5EA, ShowMsg.class, null), // Request/Response /show + SHOWVAULTINVENTORY(0xD1FB4842, null, null), // Show Vault Inventory + SOCIALCHANNEL(0x2BF58FA6, SocialMsg.class, null), // Socials + STANDARDALERT(0xFA0A24BB, ErrorPopupMsg.class, null), //Popup messages + STUCK(0x3D04AF3A, StuckCommandMsg.class, null), // /Stuck Command + SWEARINGUILD(0x389B66B1, SwearInGuildMsg.class, SwearInGuildHandler.class), + SYNC(0x49ec109f, null, null), //Client/Server loc sync + SYSTEMBROADCASTCHANNEL(0x2FAD89D1, ChatSystemMsg.class, null), // Chat Channel: System Message + SYSTEMCHANNEL(0x29BB4D66, ChatSystemChannelMsg.class, null), // Chat System Channel + TARGETEDACTION(0xB79BA48F, TargetedActionMsg.class, null), //Message sent for attacks + TAXCITY(0xCD41EAA6, TaxCityMsg.class, TaxCityMsgHandler.class), + TAXRESOURCES(0x4AD458AF, TaxResourcesMsg.class, TaxResourcesMsgHandler.class), + TELEPORT(0x23E726EA, TeleportToPointMsg.class, null), // Teleport to point + TERRITORYCHANGE(0x6B388C8C, TerritoryChangeMessage.class, null), //Hey rich, look what I found? :) + TOGGLESITSTAND(0x624F3C0F, ToggleSitStandMsg.class, null), //Toggle Sit/Stand + TRADEADDGOLD(0x654ACB45, AddGoldToTradeWindowMsg.class, null), // Add Gold to Trade Window + TRADEADDOBJECT(0x55D363E9, AddItemToTradeWindowMsg.class, null), // Add an Item to the Trade Window + TRADECLOSE(0x5008D7FC, CloseTradeWindowMsg.class, null), // Cancel trade/ACK trade complete + TRADECONFIRM(0x6911E65E, CommitToTradeMsg.class, null), // Commit to trade + TRADECONFIRMSTATUS(0x9F85DAFC, null, null), // Other player commit/uncommit/add item + TRADEUNCONFIRM(0xEBE280E0, UncommitToTradeMsg.class, null), // Uncommit to trade + TRAINERLIST(0x41FABA62, TrainerInfoMsg.class, null), //Req/Send Trainer Info/Pricing + TRAINSKILL(0xB0BF68CD, TrainMsg.class, null), //Train skills/powers + TRANSFERASSET(0x3EA1C4C9, TransferAssetMsg.class, TransferAssetMsgHandler.class), // Transfer Building + TRANSFERGOLDFROMVAULTTOINVENTORY(0x011D0123, TransferGoldFromVaultToInventoryMsg.class, null), // Transfer Gold from Vault to Inventory + TRANSFERGOLDTOFROMBUILDING(0x1B1AC8C7, TransferGoldToFromBuildingMsg.class, TransferGoldToFromBuildingMsgHandler.class), // Transfer Gold to/From Building, Transfer Error + TRANSFERITEMFROMBANK(0x9D04977B, TransferItemFromBankToInventoryMsg.class, null), // Transfer Item from Bank to Inventory + TRANSFERITEMFROMVAULTTOINVENTORY(0x0119A64D, TransferItemFromVaultToInventoryMsg.class, null), // Transfer Item from Vault to Inventory + TRANSFERITEMTOBANK(0xD48C46FA, TransferItemFromInventoryToBankMsg.class, null), // Transfer Item from Inventory to Bank + UNEQUIP(0xC6BFB907, TransferItemFromEquipToInventoryMsg.class, null), // Transfer Item from Equip to Inventory + UNKNOWN(0x238C9259, UnknownMsg.class, null), + UPDATECHARORMOB(0xB6D78961, null, null), + UPDATECLIENTALLIANCES(0xF3FEB5D4, null, GuildUnknownHandler.class), //AlliancesMsg + UPDATECLIENTINVENTORIES(0xE66F533D, UpdateInventoryMsg.class, null), //Update player inventory + UPDATEEFFECTS(0xD4675293, null, null), //Update all effects for an item + UPDATEFRIENDSTATUS(0x654E2255, UpdateFriendStatusMessage.class, UpdateFriendStatusHandler.class), + UPDATEGOLDVALUE(0x6915A3FB, null, null), // Update gold in inventory and/or bank + UPDATEGROUP(0x004E6BCE, GroupUpdateMsg.class, GroupUpdateHandler.class), // Update Group Info + UPDATEGUILD(0x001D4DF6, GuildInfoMsg.class, GuildInfoHandler.class), // REQ / CMD Promote/Demote Screen + UPDATEOBJECT(0x1A724739, null, null), + UPDATESTATE(0x001A45FB, UpdateStateMsg.class, null), // REQ / CMD Toggle Run/Walk Sit/Stand :: UpdateStateMessage + UPDATETRADEWINDOW(0x406EBDE6, UpdateTradeWindowMsg.class, null), // Trade Complete + UPGRADEASSET(0x2B85A865, UpgradeAssetMessage.class, UpgradeAssetMsgHandler.class), + VENDORDIALOG(0x98ACD594, VendorDialogMsg.class, null), // Send/Recv Vendor Dialog + VERSIONINFO(0x4B7EE463, VersionInfoMsg.class, null), // Version Information + VIEWRESOURCES(0xCEFD0346, ViewResourcesMessage.class, null), + VISUALUPDATE(0x33402fd2, null, null), + WEIGHTINVENTORY(0xF1B6A85C, LootWindowResponseMsg.class, null), // MoveObjectToContainer Window Response + WHOREQUEST(0xF431CCE9, WhoRequestMsg.class, null), // Request /who + WHORESPONSE(0xD7C36568, WhoResponseMsg.class, null), // Response /who + REQUESTBALLLIST(0xE366FF64, RequestBallListMessage.class, RequestBallListHandler.class), + SENDBALLENTRY(0xAC2B5EDC, SendBallEntryMessage.class, SendBallEntryHandler.class), + UNKNOWN1(-263523523, Unknown1Msg.class, null), + DROPGOLD(1461654160, DropGoldMsg.class, null); + + private static final HashMap _protocolMsgByOpcode = new HashMap<>(); + private final Class message; + private final Class handlerClass; + public int opcode; + public Constructor constructor; + public AbstractClientMsgHandler handler; + + Protocol(int opcode, Class message, Class handlerClass) { + this.opcode = opcode; + this.message = message; + this.handlerClass = handlerClass; + + // Create reference to message class constructor. + + if (this.message != null) { + Class[] params = {AbstractConnection.class, ByteBufferReader.class}; + + try { + this.constructor = this.message.getConstructor(params); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } + + // Create instance of message handler for incoming protocol messages + + if (this.handlerClass != null) { + try { + handler = (AbstractClientMsgHandler) handlerClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + public static Protocol getByOpcode(int opcode) { + + Protocol protocol = _protocolMsgByOpcode.get(opcode); + + if (protocol != null) + return protocol; + + return Protocol.NONE; + } + + public static void initProtocolLookup() { + + for (Protocol protocol : Protocol.values()) { + + if (_protocolMsgByOpcode.containsKey(protocol.opcode)) { + Logger.error("Duplicate opcodes for " + protocol.name() + " and " + _protocolMsgByOpcode.get(protocol.opcode).name()); + } + _protocolMsgByOpcode.put(protocol.opcode, protocol); + } + } + + + public static int FindNextValidOpcode(ByteBufferReader reader) { + int startPos = reader.position(); + int bytesLeft = reader.remaining(); + + if (bytesLeft < 4) + return startPos; + int nextPos = startPos; + for (int i = 1; i < bytesLeft; i++) { + reader.position(nextPos); + if (reader.remaining() < 4) + return reader.position(); + int newOpcode = reader.getInt(); + + Protocol foundProtocol = Protocol.getByOpcode(newOpcode); + if (foundProtocol.equals(Protocol.NONE)) { + nextPos += 1; + continue; + } + + //found opcode. return position - 4 to rewind back to start of opcode, so we can handle it. + return reader.position() - 4; + } + + return startPos; + } +} diff --git a/src/engine/net/client/handlers/AbandonAssetMsgHandler.java b/src/engine/net/client/handlers/AbandonAssetMsgHandler.java new file mode 100644 index 00000000..168afbf0 --- /dev/null +++ b/src/engine/net/client/handlers/AbandonAssetMsgHandler.java @@ -0,0 +1,217 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.GuildState; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.net.client.ClientConnection; +import engine.net.client.msg.AbandonAssetMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which processes + * client requests to abandon a building. + */ +public class AbandonAssetMsgHandler extends AbstractClientMsgHandler { + + // Instance variables + + public AbandonAssetMsgHandler() { + super(AbandonAssetMsg.class); + + } + + private static void AbandonSingleAsset(PlayerCharacter sourcePlayer, + Building targetBuilding) { + + // Transfer the building asset ownership and refresh all clients + + DbManager.BuildingQueries.CLEAR_FRIENDS_LIST(targetBuilding.getObjectUUID()); + targetBuilding.getFriends().clear(); + + // Clear protection status but only if a seige building + + if (targetBuilding.getBlueprint().getBuildingGroup().equals(BuildingGroup.BULWARK) || + targetBuilding.getBlueprint().getBuildingGroup().equals(BuildingGroup.SIEGETENT)) + targetBuilding.setProtectionState(Enum.ProtectionState.NONE); + + DbManager.BuildingQueries.CLEAR_CONDEMNED_LIST(targetBuilding.getObjectUUID()); + targetBuilding.getCondemned().clear(); + targetBuilding.setOwner(null); + targetBuilding.refreshGuild(); + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + PlayerCharacter player; + Building building; + AbandonAssetMsg msg; + + // Member variable assignment + msg = (AbandonAssetMsg) baseMsg; + + player = origin.getPlayerCharacter(); + building = BuildingManager.getBuildingFromCache(msg.getUUID()); + + // Oops! *** Refactor: Log error + if ((player == null) || (building == null)) + return true; + + // Early exit if object is not owned by the player + if (building.getOwnerUUID() != player.getObjectUUID()) + return true; + + // Cannot abandon a building without a blueprint. + // Players do not own rocks or shrubbery. + if (building.getBlueprintUUID() == 0) + return true; + + // Players cannot abandon shrines + + if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon shrine!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.MINE)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot abandon mine!"); + return true; + } + + if (Blueprint.isMeshWallPiece(building.getBlueprintUUID())) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon fortress asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon fortress asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.BULWARK)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon siege asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.SIEGETENT)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon siege asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.BANESTONE)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon banestone!"); + return true; + } + + // Trees require special handling beyond an individual building + if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL)) { + // CHECK IF GUILD HAS A BANE DROPPED + City city = ZoneManager.getCityAtLocation(building.getLoc()); + if (city.getGuild().getSubGuildList().isEmpty() == false) { + //nations cant abandon their tree + ErrorPopupMsg.sendErrorMsg(player, "Nations Cannot Abandon Their Capital!"); + return true; + } + if (Bane.getBaneByAttackerGuild(city.getGuild()) != null) { + ErrorPopupMsg.sendErrorMsg(player, "You Cannot Abandon Your Tree With An Active Siege!"); + return true; + } + + AbandonAllCityObjects(player, building); + } else + AbandonSingleAsset(player, building); + + return true; + } + + private void AbandonAllCityObjects(PlayerCharacter sourcePlayer, + Building targetBuilding) { + Guild sourceGuild; + Zone cityZone; + + sourceGuild = sourcePlayer.getGuild(); + + if (sourceGuild == null) + return; + + if (sourceGuild.getSubGuildList().size() > 0) { + ChatManager.chatCityError(sourcePlayer, "You Cannot abandon a nation city."); + return; + } + + + cityZone = ZoneManager.findSmallestZone(targetBuilding.getLoc()); + + // Can't abandon a tree not within a player city zone + if (cityZone.isPlayerCity() == false) + return; + + if (targetBuilding.getCity() == null) + return; + + if (targetBuilding.getCity().getBane() != null) { + ErrorPopupMsg.sendErrorMsg(sourcePlayer, "Can't abandon Tree while a bane exists."); + return; + } + + if (targetBuilding.getCity().hasBeenTransfered == true) { + ChatManager.chatCityError(sourcePlayer, "City can only be abandoned once per rebooting."); + return; + } + + // Guild no longer owns his tree. + if (!DbManager.GuildQueries.SET_GUILD_OWNED_CITY(sourceGuild.getObjectUUID(), 0)) { + Logger.error("Failed to update Owned City to Database"); + return; + } + + sourceGuild.setCityUUID(0); + sourceGuild.setGuildState(GuildState.Errant); + sourceGuild.setNation(null); + + // Transfer the city assets + TransferCityAssets(sourcePlayer, targetBuilding); + + GuildManager.updateAllGuildTags(sourceGuild); + GuildManager.updateAllGuildBinds(sourceGuild, null); + + } + + private void TransferCityAssets(PlayerCharacter sourcePlayer, + Building cityTOL) { + + Zone cityZone; + + // Build list of buildings within this parent zone + cityZone = ZoneManager.findSmallestZone(cityTOL.getLoc()); + + for (Building cityBuilding : cityZone.zoneBuildingSet) { + + Blueprint cityBlueprint; + cityBlueprint = cityBuilding.getBlueprint(); + + // Buildings without blueprints cannot be abandoned + if (cityBlueprint == null) + continue; + + // Transfer ownership of valid city assets + if ((cityBlueprint.getBuildingGroup() == BuildingGroup.TOL) + || (cityBlueprint.getBuildingGroup() == BuildingGroup.SPIRE) + || (cityBlueprint.getBuildingGroup() == BuildingGroup.BARRACK) + || (cityBlueprint.isWallPiece()) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) + AbandonSingleAsset(sourcePlayer, cityBuilding); + + } + + } + +} diff --git a/src/engine/net/client/handlers/AbstractClientMsgHandler.java b/src/engine/net/client/handlers/AbstractClientMsgHandler.java new file mode 100644 index 00000000..d3045e3d --- /dev/null +++ b/src/engine/net/client/handlers/AbstractClientMsgHandler.java @@ -0,0 +1,34 @@ +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; + +/* @Summary: This is the abstract class from which all message handlers + * for mainline application protocol derive. Namely those + * routed and executed via ClientMessageHandler. + */ + +public abstract class AbstractClientMsgHandler { + private final Class handler; + + public AbstractClientMsgHandler(Class handler) { + this.handler = handler; + } + + public boolean handleNetMsg(ClientNetMsg msg) { + + boolean executionSucceded; + + try { + executionSucceded = _handleNetMsg(msg, (ClientConnection) msg.getOrigin()); + } catch (MsgSendException e) { + e.printStackTrace(); + executionSucceded = false; + } + + return executionSucceded; + } + + protected abstract boolean _handleNetMsg(ClientNetMsg msg, ClientConnection origin) throws MsgSendException; +} diff --git a/src/engine/net/client/handlers/AcceptInviteToGuildHandler.java b/src/engine/net/client/handlers/AcceptInviteToGuildHandler.java new file mode 100644 index 00000000..a0269277 --- /dev/null +++ b/src/engine/net/client/handlers/AcceptInviteToGuildHandler.java @@ -0,0 +1,110 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.GuildHistoryType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.AcceptInviteToGuildMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.objects.Guild; +import engine.objects.GuildHistory; +import engine.objects.PlayerCharacter; +import org.joda.time.DateTime; + +public class AcceptInviteToGuildHandler extends AbstractClientMsgHandler { + + public AcceptInviteToGuildHandler() { + super(AcceptInviteToGuildMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + AcceptInviteToGuildMsg msg; + Guild guild; + + msg = (AcceptInviteToGuildMsg) baseMsg; + + // get PlayerCharacter of person accepting invite + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + guild = (Guild) DbManager.getObject(GameObjectType.Guild, msg.getGuildUUID()); + + + if (guild == null) + return true; + + if (guild.getGuildType() == null) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND); + return true; + } + + // verify they accepted for the correct guild + + if (player.getLastGuildToInvite() != msg.getGuildUUID()) + return true; + + if ((player.getGuild() != null) && + (player.getGuild().isEmptyGuild() == false)) { + ChatManager.chatGuildError(player, + "You already belongs to a guild!"); + return true; + } + + // verify they are acceptable level for guild + + if (player.getLevel() < guild.getRepledgeMin() || player.getLevel() > guild.getRepledgeMax()) + return true; + + // Add player to guild + player.setGuild(guild); + + // Cleanup guild stuff + player.resetGuildStatuses(); + + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + DispatchMessage.sendToAllInRange(player, new GuildInfoMsg(player, guild, 2)); + + player.incVer(); + + //Add to guild History + + if (player.getGuild() != null) { + if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(player.getGuildUUID(), player, DateTime.now(), GuildHistoryType.JOIN)) { + GuildHistory guildHistory = new GuildHistory(player.getGuildUUID(), player.getGuild().getName(), DateTime.now(), GuildHistoryType.JOIN); + player.getGuildHistory().add(guildHistory); + } + } + + // Send guild join message + + ChatManager.chatGuildInfo(player, player.getFirstName() + " has joined the guild"); + return true; + } + +} diff --git a/src/engine/net/client/handlers/AcceptSubInviteHandler.java b/src/engine/net/client/handlers/AcceptSubInviteHandler.java new file mode 100644 index 00000000..d70b0b2e --- /dev/null +++ b/src/engine/net/client/handlers/AcceptSubInviteHandler.java @@ -0,0 +1,106 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.GuildState; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.AcceptSubInviteMsg; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +import java.util.ArrayList; + +public class AcceptSubInviteHandler extends AbstractClientMsgHandler { + + public AcceptSubInviteHandler() { + super(AcceptSubInviteMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + AcceptSubInviteMsg msg = (AcceptSubInviteMsg) baseMsg; + PlayerCharacter sourcePlayer; + Guild sourceGuild; + Guild targetGuild; + Dispatch dispatch; + + // get PlayerCharacter of person sending sub invite + + sourcePlayer = SessionManager.getPlayerCharacter(origin); + + if (sourcePlayer == null) + return true; + + sourceGuild = sourcePlayer.getGuild(); + targetGuild = (Guild) DbManager.getObject(GameObjectType.Guild, msg.guildUUID()); + + //must be source guild to sub to + + if (targetGuild == null) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 45); // Failure to swear guild + return true; + } + if (sourceGuild == null) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 45); // Failure to swear guild + return true; + } + + if (sourceGuild.equals(targetGuild)) + return true; + + if (GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(sourcePlayer, "Only a guild leader can accept fealty!"); + return true; + } + + //source guild is limited to 7 subs + //TODO this should be based on TOL rank + + if (!targetGuild.canSubAGuild(sourceGuild)) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 45); // Failure to swear guild + return true; + } + + //all tests passed, let's Handle code + //Update Target Guild State. + + sourceGuild.upgradeGuildState(false); + + //Add sub so GuildMaster can Swear in. + + ArrayList subs = targetGuild.getSubGuildList(); + subs.add(sourceGuild); + + targetGuild.setGuildState(GuildState.Nation); + + + //Let's send the message back. + + msg.setUnknown02(1); + msg.setResponse("Your guild is now a " + sourceGuild.getGuildState().name() + '.'); + dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + ChatManager.chatSystemInfo(sourcePlayer, "Your guild is now a " + sourceGuild.getGuildState().name() + '.'); + return true; + } +} diff --git a/src/engine/net/client/handlers/ActivateNPCMsgHandler.java b/src/engine/net/client/handlers/ActivateNPCMsgHandler.java new file mode 100644 index 00000000..0d2897bf --- /dev/null +++ b/src/engine/net/client/handlers/ActivateNPCMsgHandler.java @@ -0,0 +1,136 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.ItemType; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ActivateNPCMessage; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ManageCityAssetsMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ +public class ActivateNPCMsgHandler extends AbstractClientMsgHandler { + + public ActivateNPCMsgHandler() { + super(ActivateNPCMessage.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + ActivateNPCMessage msg; + PlayerCharacter player; + Building building; + Contract contract; + CharacterItemManager itemMan; + Zone zone; + + msg = (ActivateNPCMessage) baseMsg; + player = SessionManager.getPlayerCharacter(origin); + building = BuildingManager.getBuildingFromCache(msg.buildingUUID()); + + if (player == null || building == null) + return false; + + ArrayList ItemLists = new ArrayList<>(); + + // Filter hirelings by slot type + + for (Item hirelings : player.getInventory()) { + if (hirelings.getItemBase().getType().equals(ItemType.CONTRACT)) { + + contract = DbManager.ContractQueries.GET_CONTRACT(hirelings.getItemBase().getUUID()); + + if (contract == null) + continue; + + if (contract.canSlotinBuilding(building)) + ItemLists.add(hirelings); + } + } + + if (msg.getUnknown01() == 1) { + //Request npc list to slot + ActivateNPCMessage anm = new ActivateNPCMessage(); + anm.setSize(ItemLists.size()); + anm.setItemList(ItemLists); + Dispatch dispatch = Dispatch.borrow(player, anm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + if (msg.getUnknown01() == 0) { + + //Slot npc + + if (building.getBlueprintUUID() == 0) { + ChatManager.chatSystemError(player, "Unable to load Blueprint for Building Mesh " + building.getMeshUUID()); + return false; + } + + if (building.getBlueprint().getMaxSlots() == building.getHirelings().size()) + return false; + + Item contractItem = Item.getFromCache(msg.getContractItem()); + + if (contractItem == null) + return false; + + if (!player.getCharItemManager().doesCharOwnThisItem(contractItem.getObjectUUID())) { + Logger.error(player.getName() + "has attempted to place Hireling : " + contractItem.getName() + "without a valid contract!"); + return false; + } + + itemMan = player.getCharItemManager(); + + zone = ZoneManager.findSmallestZone(building.getLoc()); + + if (zone == null) + return false; + + contract = DbManager.ContractQueries.GET_CONTRACT(contractItem.getItemBase().getUUID()); + + if (contract == null) + return false; + + // Check if contract can be slotted in this building + + if (contract.canSlotinBuilding(building) == false) + return false; + + if (!BuildingManager.addHireling(building, player, zone, contract, contractItem)) + return false; + + itemMan.delete(contractItem); + itemMan.updateInventory(); + + ManageCityAssetsMsg mca1 = new ManageCityAssetsMsg(player, building); + + mca1.actionType = 3; + + mca1.setTargetType(building.getObjectType().ordinal()); + mca1.setTargetID(building.getObjectUUID()); + mca1.setTargetType3(building.getObjectType().ordinal()); + mca1.setTargetID3(building.getObjectUUID()); + mca1.setAssetName1(building.getName()); + mca1.setUnknown54(1); + Dispatch dispatch = Dispatch.borrow(player, mca1); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + } + + return true; + } + +} diff --git a/src/engine/net/client/handlers/AllianceChangeMsgHandler.java b/src/engine/net/client/handlers/AllianceChangeMsgHandler.java new file mode 100644 index 00000000..f56b8849 --- /dev/null +++ b/src/engine/net/client/handlers/AllianceChangeMsgHandler.java @@ -0,0 +1,153 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.AllianceType; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.AllianceChangeMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * protecting and unprotecting city assets + */ +public class AllianceChangeMsgHandler extends AbstractClientMsgHandler { + + public AllianceChangeMsgHandler() { + super(AllianceChangeMsg.class); + } + + private static void MakeEnemy(Guild fromGuild, Guild toGuild, AllianceChangeMsg msg, ClientConnection origin) { + + // Member variable declaration + Dispatch dispatch; + + // Member variable assignment + + if (fromGuild == null) + return; + + if (toGuild == null) + return; + + if (!Guild.sameGuild(origin.getPlayerCharacter().getGuild(), fromGuild)) { + msg.setMsgType(AllianceChangeMsg.ERROR_NOT_SAME_GUILD); + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return; + } + + if (!GuildStatusController.isInnerCouncil(origin.getPlayerCharacter().getGuildStatus()) && !GuildStatusController.isGuildLeader(origin.getPlayerCharacter().getGuildStatus())) { + msg.setMsgType(AllianceChangeMsg.ERROR_NOT_AUTHORIZED); + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return; + } + + + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + } + + private static void makeAlly(Guild fromGuild, Guild toGuild, AllianceChangeMsg msg, ClientConnection origin) { + + // Member variable declaration + Dispatch dispatch; + + // Member variable assignment + + if (fromGuild == null) + return; + + if (toGuild == null) + return; + + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + } + + private static void removeFromAlliance(Guild fromGuild, Guild toGuild, AllianceChangeMsg msg, ClientConnection origin) { + + // Member variable declaration + Dispatch dispatch; + + // Member variable assignment + + if (fromGuild == null) + return; + + if (toGuild == null) + return; + + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + AllianceChangeMsg msg; + + + // Member variable assignment + + msg = (AllianceChangeMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + + Guild toGuild = null; + toGuild = Guild.getGuild(msg.getSourceGuildID()); + if (toGuild.isEmptyGuild()) + return true; + + if (player.getGuild().isEmptyGuild()) + return true; + + + switch (msg.getMsgType()) { + case AllianceChangeMsg.MAKE_ALLY: + case 1: //allyfromRecommended + player.getGuild().addGuildToAlliance(msg, AllianceType.Ally, toGuild, player); + break; + case AllianceChangeMsg.MAKE_ENEMY: + case 2: //enemy recommend + player.getGuild().addGuildToAlliance(msg, AllianceType.Enemy, toGuild, player); + break; + case 3: + case 5: + case 7: + player.getGuild().removeGuildFromAllAlliances(toGuild); + break; + + } + msg.setMsgType(AllianceChangeMsg.INFO_SUCCESS); + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + return true; + + } + + +} diff --git a/src/engine/net/client/handlers/AllyEnemyListMsgHandler.java b/src/engine/net/client/handlers/AllyEnemyListMsgHandler.java new file mode 100644 index 00000000..18fc7b48 --- /dev/null +++ b/src/engine/net/client/handlers/AllyEnemyListMsgHandler.java @@ -0,0 +1,78 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.AllyEnemyListMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Guild; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * protecting and unprotecting city assets + */ +public class AllyEnemyListMsgHandler extends AbstractClientMsgHandler { + + public AllyEnemyListMsgHandler() { + super(AllyEnemyListMsg.class); + } + + private static void showAllyEnemyList(Guild fromGuild, Guild toGuild, AllyEnemyListMsg msg, ClientConnection origin) { + + // Member variable declaration + Dispatch dispatch; + + // Member variable assignment + + if (fromGuild == null) + return; + + if (toGuild == null) + return; + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + // UpdateClientAlliancesMsg ucam = new UpdateClientAlliancesMsg(); + // + // dispatch = Dispatch.borrow(origin.getPlayerCharacter(), ucam); + // DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + AllyEnemyListMsg msg; + + + // Member variable assignment + + msg = (AllyEnemyListMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + + AllyEnemyListMsgHandler.showAllyEnemyList(player.getGuild(), Guild.getGuild(msg.getGuildID()), msg, origin); + + + // dispatch = Dispatch.borrow(player, baseMsg); + // DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + + } + + +} diff --git a/src/engine/net/client/handlers/AppointGroupLeaderHandler.java b/src/engine/net/client/handlers/AppointGroupLeaderHandler.java new file mode 100644 index 00000000..b7e1f42f --- /dev/null +++ b/src/engine/net/client/handlers/AppointGroupLeaderHandler.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.GroupManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.AppointGroupLeaderMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +public class AppointGroupLeaderHandler extends AbstractClientMsgHandler { + + public AppointGroupLeaderHandler() { + super(AppointGroupLeaderMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + AppointGroupLeaderMsg msg = (AppointGroupLeaderMsg) baseMsg; + + PlayerCharacter source = SessionManager.getPlayerCharacter(origin); + if (source == null) { + return false; + } + Group group = GroupManager.getGroup(source); + if (group == null) { + return false; + } + if (group.getGroupLead() != source) { + return false; + } + PlayerCharacter target = SessionManager.getPlayerCharacterByID(msg.getTargetID()); + if (target == null) { + return false; + } + if (target == source) { // Can't appoint self leader + AppointGroupLeaderMsg reply = new AppointGroupLeaderMsg(); + reply.setResponse(1); + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), reply); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + return false; + } + + // Change Group Leader + if (!group.setGroupLead(target.getObjectUUID())) { + return false; // failed to update group leader + } + // Refresh everyones group list + GroupUpdateMsg gim = new GroupUpdateMsg(); + gim.setGroup(group); + gim.setPlayer(target); + gim.setMessageType(2); + gim.addPlayer(source); + + group.sendUpdate(gim); + + // Disable Formation + target.setFollow(false); + gim = new GroupUpdateMsg(); + gim.setGroup(group); + gim.setPlayer(target); + gim.setMessageType(8); + group.sendUpdate(gim); + + String text = target.getFirstName() + " is the new group leader."; + ChatManager.chatGroupInfo(source, text); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/ArcLoginNotifyMsgHandler.java b/src/engine/net/client/handlers/ArcLoginNotifyMsgHandler.java new file mode 100644 index 00000000..02b0518c --- /dev/null +++ b/src/engine/net/client/handlers/ArcLoginNotifyMsgHandler.java @@ -0,0 +1,149 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.job.JobScheduler; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ArcLoginNotifyMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.HotzoneChangeMsg; +import engine.net.client.msg.PetMsg; +import engine.objects.Account; +import engine.objects.Guild; +import engine.objects.PlayerCharacter; +import engine.objects.PlayerFriends; +import engine.server.MBServerStatics; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +public class ArcLoginNotifyMsgHandler extends AbstractClientMsgHandler { + + public ArcLoginNotifyMsgHandler() { + super(ArcLoginNotifyMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + + if (player == null) { + Logger.error(ConfigManager.MB_WORLD_NAME.getValue() + ".EnterWorld", "Unable to find player for session"); + origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Player not found."); + return true; + } + + // cancel logout Timer if exists + if (player.getTimers().containsKey("Logout")) { + + JobScheduler.getInstance().cancelScheduledJob(player.getTimers().get("Logout")); + player.getTimers().remove("Logout"); + } + player.setTimeStamp("logout", 0); + + // refresh group window if still in group for both this player + // and everyone else in the group + + if (GroupManager.getGroup(player) != null) { + GroupManager.RefreshMyGroupList(player, origin); + GroupManager.RefreshOthersGroupList(player); + } + + player.setEnteredWorld(true); + // Set player active + player.resetRegenUpdateTime(); + player.setActive(true); + + //player.sendAllEffects(player.getClientConnection()); + // Send Enter world message to guild + + ChatManager.GuildEnterWorldMsg(player, origin); + + // Send Guild, Nation and IC MOTD + GuildManager.enterWorldMOTD(player); + ChatManager.sendSystemMessage(player, ConfigManager.MB_WORLD_GREETING.getValue()); + + // Send branch string if available from ConfigManager. + + if (ConfigManager.currentRepoBranch != "") + ChatManager.sendSystemMessage(player, ConfigManager.currentRepoBranch); + + // Set player mask for QT + if (player.getRace() != null && player.getRace().getToken() == -524731385) + player.setObjectTypeMask(MBServerStatics.MASK_PLAYER | MBServerStatics.MASK_UNDEAD); + else + player.setObjectTypeMask(MBServerStatics.MASK_PLAYER); + + // If player not already in world, then set them to bind loc and add + // to world + + if (player.newChar) + player.newChar = false; // TODO Fix safe mode + + // PowersManager.applyPower(player, player, new + // Vector3f(0f, 0f, 0f), -1661758934, 50, false); + + // Add player to the QT for tracking + + player.setLoc(player.getLoc()); + + //send online status to friends. + PlayerFriends.SendFriendsStatus(player, true); + + // Handle too many simultaneous logins from the same forum account by disconnecting the other account(s) + + Account thisAccount = SessionManager.getAccount(player); + int maxAccounts = MBServerStatics.MAX_ACTIVE_GAME_ACCOUNTS_PER_DISCORD_ACCOUNT; + + if (maxAccounts > 0) { + + int count = 1; + for (Account othAccount : SessionManager.getAllActiveAccounts()) { + + if (othAccount.equals(thisAccount)) + continue; + + if (thisAccount.discordAccount.equals(othAccount.discordAccount) == false) + continue; + + count++; + + if (count > maxAccounts) { + Session otherSession = SessionManager.getSession(othAccount); + if (otherSession != null) { + ClientConnection otherConn = otherSession.getConn(); + if (otherConn != null) { + ChatManager.chatSystemInfo(player, "Only 4 accounts may be used simultaneously. Account '" + othAccount.getUname() + "' has been disconnected."); + otherConn.disconnect(); + } + } + } + } + } + + player.setTimeStamp("logout", 0); + + if (player.getPet() != null) { + PetMsg pm = new PetMsg(5, player.getPet()); + Dispatch dispatch = Dispatch.borrow(player, pm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + //Send current hotzone + + if (ZoneManager.hotZone != null) { + HotzoneChangeMsg hcm = new HotzoneChangeMsg(Enum.GameObjectType.Zone.ordinal(), ZoneManager.hotZone.getObjectUUID()); + Dispatch dispatch = Dispatch.borrow(player, hcm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + if (player.getGuild() != null && !player.getGuild().isEmptyGuild()) { + Guild.UpdateClientAlliancesForPlayer(player); + } + return true; + } +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/ArcMineChangeProductionMsgHandler.java b/src/engine/net/client/handlers/ArcMineChangeProductionMsgHandler.java new file mode 100644 index 00000000..67e42527 --- /dev/null +++ b/src/engine/net/client/handlers/ArcMineChangeProductionMsgHandler.java @@ -0,0 +1,67 @@ +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ArcMineChangeProductionMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.GuildStatusController; +import engine.objects.Mine; +import engine.objects.PlayerCharacter; +import engine.objects.Resource; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ + +public class ArcMineChangeProductionMsgHandler extends AbstractClientMsgHandler { + + public ArcMineChangeProductionMsgHandler() { + super(ArcMineChangeProductionMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter playerCharacter = origin.getPlayerCharacter(); + ArcMineChangeProductionMsg changeProductionMsg = (ArcMineChangeProductionMsg) baseMsg; + + if (playerCharacter == null) + return true; + + //TODO verify this against the warehouse? + + if (GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()) == false) // is this only GL? + return true; + + Mine mine = Mine.getMine(changeProductionMsg.getMineID()); + + if (mine == null) + return true; + + //make sure mine belongs to guild + + if (mine.getOwningGuild().isEmptyGuild() || + mine.getOwningGuild().getObjectUUID() != playerCharacter.getGuild().getObjectUUID()) + return true; + + //make sure valid resource + + Resource resource = Resource.resourceByHash.get(changeProductionMsg.getResourceHash()); + + if (resource == null) + return true; + + //update resource + + mine.changeProductionType(resource); + Dispatch dispatch = Dispatch.borrow(playerCharacter, changeProductionMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/ArcMineWindowAvailableTimeHandler.java b/src/engine/net/client/handlers/ArcMineWindowAvailableTimeHandler.java new file mode 100644 index 00000000..6b2bd0ee --- /dev/null +++ b/src/engine/net/client/handlers/ArcMineWindowAvailableTimeHandler.java @@ -0,0 +1,62 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ArcMineWindowAvailableTimeMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Building; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ + +public class ArcMineWindowAvailableTimeHandler extends AbstractClientMsgHandler { + + public ArcMineWindowAvailableTimeHandler() { + super(ArcMineWindowAvailableTimeMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + ArcMineWindowAvailableTimeMsg msg = (ArcMineWindowAvailableTimeMsg) baseMsg; + PlayerCharacter playerCharacter = origin.getPlayerCharacter(); + Building treeOfLife = BuildingManager.getBuildingFromCache(msg.getBuildingUUID()); + Dispatch dispatch; + + if (treeOfLife == null) + return true; + + if (treeOfLife.getBlueprintUUID() == 0) + return true; + + if (treeOfLife.getBlueprint().getBuildingGroup() != Enum.BuildingGroup.TOL) + return true; + + if (playerCharacter == null) + return true; + + if (!Guild.sameGuild(treeOfLife.getGuild(), playerCharacter.getGuild())) + return true; + + if (GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()) == false) // is this only GL? + return true; + + ArcMineWindowAvailableTimeMsg outMsg = new ArcMineWindowAvailableTimeMsg(treeOfLife, 10); + outMsg.configure(); + dispatch = Dispatch.borrow(playerCharacter, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + + } +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/ArcOwnedMinesListHandler.java b/src/engine/net/client/handlers/ArcOwnedMinesListHandler.java new file mode 100644 index 00000000..10f7f802 --- /dev/null +++ b/src/engine/net/client/handlers/ArcOwnedMinesListHandler.java @@ -0,0 +1,45 @@ +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ArcOwnedMinesListMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.GuildStatusController; +import engine.objects.Mine; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ + +public class ArcOwnedMinesListHandler extends AbstractClientMsgHandler { + + public ArcOwnedMinesListHandler() { + super(ArcOwnedMinesListMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter playerCharacter = origin.getPlayerCharacter(); + ArcOwnedMinesListMsg msg = (ArcOwnedMinesListMsg) baseMsg; + + if (playerCharacter == null) + return true; + + if (GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()) == false)// is this only GL? + return true; + + msg.setMineList(Mine.getMinesForGuild(playerCharacter.getGuild().getObjectUUID())); + Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/ArcSiegeSpireMsgHandler.java b/src/engine/net/client/handlers/ArcSiegeSpireMsgHandler.java new file mode 100644 index 00000000..322981ad --- /dev/null +++ b/src/engine/net/client/handlers/ArcSiegeSpireMsgHandler.java @@ -0,0 +1,120 @@ +package engine.net.client.handlers; + +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ArcSiegeSpireMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.Building; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which requests that + * siege spires be toggled on or off. + */ + +public class ArcSiegeSpireMsgHandler extends AbstractClientMsgHandler { + + public ArcSiegeSpireMsgHandler() { + super(ArcSiegeSpireMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + Building spire; + ArcSiegeSpireMsg msg; + + msg = (ArcSiegeSpireMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + spire = (Building) DbManager.getObject(GameObjectType.Building, msg.getBuildingUUID()); + + if (spire == null) + return true; + + if (spire.getCity() == null) + return true; + + spire.getCity().transactionLock.writeLock().lock(); + + try { + + + //can't activate a spire that's not rank 1. + + if (spire.getRank() < 1) + return true; + + // can't activate a spire without a city + + if (spire.getCity() == null) + return true; + + // Must have management authority for the spire + + if ((player.getGuild().equals(spire.getGuild()) == false) + || (GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false)) + return true; + + // Handle case where spire is sabotaged + + if (spire.getTimeStamp("DISABLED") > System.currentTimeMillis()) { + ErrorPopupMsg.sendErrorPopup(player, 174); //This siege spire cannot be toggled yet. + return true; + } + + // Handle case where spire's toggle delay hasn't yet passed + + if (spire.getTimeStamp("TOGGLEDELAY") > System.currentTimeMillis()) { + ErrorPopupMsg.sendErrorPopup(player, 174); //This siege spire cannot be toggled yet. + return true; + } + + // This protocol message is a toggle. If it's currently active then disable + // the spire. + + if (spire.isSpireIsActive()) { + spire.disableSpire(false); + return true; + } + + // Must be enough gold on the spire to turn it on + + if (!spire.hasFunds(5000)) { + ErrorPopupMsg.sendErrorPopup(player, 127); // Not enough gold in strongbox + return true; + } + + if (spire.getStrongboxValue() < 5000) { + ErrorPopupMsg.sendErrorPopup(player, 127); // Not enough gold in strongbox + return true; + } + + spire.transferGold(-5000, false); + spire.enableSpire(); + + // Spire is now enabled. Reset the toggle delay + + spire.setTimeStamp("TOGGLEDELAY", System.currentTimeMillis() + (long) 10 * 60 * 1000); + } catch (Exception e) { + Logger.error(e); + } finally { + spire.getCity().transactionLock.writeLock().unlock(); + } + return true; + + } + +} diff --git a/src/engine/net/client/handlers/ArcViewAssetTransactionsMsgHandler.java b/src/engine/net/client/handlers/ArcViewAssetTransactionsMsgHandler.java new file mode 100644 index 00000000..468a3796 --- /dev/null +++ b/src/engine/net/client/handlers/ArcViewAssetTransactionsMsgHandler.java @@ -0,0 +1,54 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ArcViewAssetTransactionsMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.PlayerCharacter; +import engine.objects.Warehouse; + +/* + * @Author: + * @Summary: Processes application protocol message which transfers + * gold between a building's strongbox and a player character. + */ + +public class ArcViewAssetTransactionsMsgHandler extends AbstractClientMsgHandler { + + public ArcViewAssetTransactionsMsgHandler() { + super(ArcViewAssetTransactionsMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + ArcViewAssetTransactionsMsg msg; + ArcViewAssetTransactionsMsg newMsg; + player = SessionManager.getPlayerCharacter(origin); + Dispatch dispatch; + + if (player == null) + return true; + + msg = (ArcViewAssetTransactionsMsg) baseMsg; + + Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(msg.getWarehouseID()); + + if (warehouse == null) + return true; + + newMsg = new ArcViewAssetTransactionsMsg(warehouse, msg); + newMsg.configure(); + + dispatch = Dispatch.borrow(player, newMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/AssetSupportMsgHandler.java b/src/engine/net/client/handlers/AssetSupportMsgHandler.java new file mode 100644 index 00000000..d7bcc3d3 --- /dev/null +++ b/src/engine/net/client/handlers/AssetSupportMsgHandler.java @@ -0,0 +1,240 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.SupportMsgType; +import engine.Enum.TaxType; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.NPCManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * protecting and unprotecting city assets + */ +public class AssetSupportMsgHandler extends AbstractClientMsgHandler { + + public AssetSupportMsgHandler() { + super(AssetSupportMsg.class); + } + + private static void protectAsset(AssetSupportMsg msg, Building targetBuilding, NPC vendor, ClientConnection origin) { + + // Member variable declaration + + Zone serverZone; + City serverCity; + ManageNPCMsg outMsg; + int protectionSlots; + Dispatch dispatch; + + // Member variable assignment + + if (targetBuilding == null) + return; + + serverZone = vendor.getParentZone(); + + if (serverZone == null) + return; + + serverCity = City.GetCityFromCache(serverZone.getPlayerCityUUID()); + + if (serverCity == null) + return; + + if (!serverCity.isLocationOnCityZone(targetBuilding.getLoc())) { + ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Structura must be on city zone for to protect."); + return; + } + + if ((serverCity.getTOL() == null) || (serverCity.getTOL().getRank() < 1)) + return; + + if (serverCity.protectionEnforced == false) { + ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Runemaster can not protect structura during bane!"); + return; + } + + if (serverCity.getRuneMaster() == null) { + ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Runemaster is needed for to protect structura!"); + return; + } + + // Enforce runemaster protection limits + + protectionSlots = (2 * serverCity.getRuneMaster().getRank()) + 6; + + if (NPCManager.getProtectedBuildings(serverCity.getRuneMaster()).size() >= + protectionSlots) { + ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Runemaster can only protect " + protectionSlots + " structura!"); + return; + } + + if (msg.getWeeklyTax() != 0) { + if (!serverCity.getTOL().addProtectionTax(targetBuilding, origin.getPlayerCharacter(), TaxType.WEEKLY, msg.getWeeklyTax(), msg.getEnforceKOS() == 1 ? true : false)) { + ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Failed to add taxes to building."); + return; + } + targetBuilding.setProtectionState(Enum.ProtectionState.PENDING); + } else if (msg.getProfitTax() != 0) { + if (!serverCity.getTOL().addProtectionTax(targetBuilding, origin.getPlayerCharacter(), TaxType.PROFIT, msg.getProfitTax(), msg.getEnforceKOS() == 1 ? true : false)) { + ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Failed to add taxes to building."); + return; + } + targetBuilding.setProtectionState(Enum.ProtectionState.PENDING); + } else + targetBuilding.setProtectionState(Enum.ProtectionState.CONTRACT); + + + outMsg = new ManageNPCMsg(vendor); + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + private static void unprotectAsset(Building targetBuilding, NPC vendor, ClientConnection origin) { + + if (targetBuilding == null) + return; + + // Early exit if UUID < the last database derived building UUID. + + if (targetBuilding.getProtectionState() == Enum.ProtectionState.NPC) { + return; + } + + if (targetBuilding.getProtectionState() == engine.Enum.ProtectionState.NONE) + return; + + if (GuildStatusController.isInnerCouncil(origin.getPlayerCharacter().getGuildStatus()) == false) + return; + + targetBuilding.removeTaxes(); + + targetBuilding.setProtectionState(engine.Enum.ProtectionState.NONE); + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + NPC vendor; + Building targetBuilding; + Dispatch dispatch; + + AssetSupportMsg msg; + CityAssetMsg outMsg; + + // Member variable assignment + + msg = (AssetSupportMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + vendor = NPC.getFromCache(msg.getNpcID()); + + if (msg.getMessageType() != 6 && msg.getMessageType() != 7) { + if (vendor == null) + return true; + + vendor.getBuilding(); + + if (vendor.getBuilding() == null) + return true; + } + + + SupportMsgType supportType = SupportMsgType.typeLookup.get(msg.getMessageType()); + + if (supportType == null) { + supportType = Enum.SupportMsgType.NONE; + Logger.error("No enumeration for support type" + msg.getMessageType()); + } + switch (supportType) { + + case PROTECT: + targetBuilding = BuildingManager.getBuildingFromCache(msg.getProtectedBuildingID()); + protectAsset(msg, targetBuilding, vendor, origin); + break; + case UNPROTECT: + targetBuilding = BuildingManager.getBuildingFromCache(msg.getProtectedBuildingID()); + unprotectAsset(targetBuilding, vendor, origin); + break; + case VIEWUNPROTECTED: + outMsg = new CityAssetMsg(); + outMsg.setBuildingID(msg.getBuildingID()); + outMsg.configure(); + + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + + case REMOVETAX: + targetBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (targetBuilding == null) + return true; + + targetBuilding.removeTaxes(); + unprotectAsset(targetBuilding, null, origin); + + ManageCityAssetsMsg mca = new ManageCityAssetsMsg(origin.getPlayerCharacter(), targetBuilding); + + // Action TYPE + mca.actionType = 3; + mca.setTargetType(targetBuilding.getObjectType().ordinal()); + mca.setTargetID(targetBuilding.getObjectUUID()); + mca.setTargetType3(targetBuilding.getObjectType().ordinal()); + mca.setTargetID3(targetBuilding.getObjectUUID()); + mca.setAssetName1(targetBuilding.getName()); + mca.setUnknown54(1); + dispatch = Dispatch.borrow(player, mca); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + + case ACCEPTTAX: //AcceptTax + + targetBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (targetBuilding == null) + return true; + + targetBuilding.acceptTaxes(); + + mca = new ManageCityAssetsMsg(origin.getPlayerCharacter(), targetBuilding); + + // Action TYPE + mca.actionType = 3; + mca.setTargetType(targetBuilding.getObjectType().ordinal()); + mca.setTargetID(targetBuilding.getObjectUUID()); + mca.setTargetType3(targetBuilding.getObjectType().ordinal()); + mca.setTargetID3(targetBuilding.getObjectUUID()); + mca.setAssetName1(targetBuilding.getName()); + mca.setUnknown54(1); + + dispatch = Dispatch.borrow(player, mca); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + } + + dispatch = Dispatch.borrow(player, baseMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + + } + +} diff --git a/src/engine/net/client/handlers/BanishUnbanishHandler.java b/src/engine/net/client/handlers/BanishUnbanishHandler.java new file mode 100644 index 00000000..e2a88482 --- /dev/null +++ b/src/engine/net/client/handlers/BanishUnbanishHandler.java @@ -0,0 +1,126 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GuildHistoryType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.BanishUnbanishMsg; +import engine.net.client.msg.guild.GuildListMsg; +import engine.objects.Guild; +import engine.objects.GuildHistory; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; +import org.joda.time.DateTime; + +public class BanishUnbanishHandler extends AbstractClientMsgHandler { + + public BanishUnbanishHandler() { + super(BanishUnbanishMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + BanishUnbanishMsg msg = (BanishUnbanishMsg) baseMsg; + Dispatch dispatch; + + int target = msg.getTarget(); + PlayerCharacter source = origin.getPlayerCharacter(); + + if (source == null || source.getGuild().isEmptyGuild() || source.getGuild().getObjectUUID() == 0) + return true; + + if (GuildStatusController.isGuildLeader(source.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(source.getGuildStatus()) == false) + return true; + + if (source.getObjectUUID() == target) { + ErrorPopupMsg.sendErrorPopup(source, 103); // You may not banish this char + return true; + } + + boolean success = false; + Guild guild = source.getGuild(); + PlayerCharacter realizedTarget = PlayerCharacter.getFromCache(target); + + if (realizedTarget != null) { + // Guild leader can't leave guild. must pass GL or disband + if (GuildStatusController.isGuildLeader(realizedTarget.getGuildStatus()) == false) { + //ICs cannot banish other ICs + if (!(GuildStatusController.isInnerCouncil(realizedTarget.getGuildStatus()) && GuildStatusController.isGuildLeader(source.getGuildStatus()) == false)) { + success = true; + if (msg.getMsgType() == 1) { + if (!DbManager.GuildQueries.ADD_TO_BANISHED_FROM_GUILDLIST(guild.getObjectUUID(), realizedTarget.getObjectUUID())) { + ChatManager.chatGuildError(source, "Failed To unbanish " + realizedTarget.getName()); + return true; + } + guild.getBanishList().remove(realizedTarget); + } else { + + if (!DbManager.GuildQueries.ADD_TO_BANISHED_FROM_GUILDLIST(guild.getObjectUUID(), realizedTarget.getObjectUUID())) { + ChatManager.chatGuildError(source, "Failed To Banish " + realizedTarget.getName()); + return true; + } + guild.removePlayer(realizedTarget, GuildHistoryType.BANISHED); + guild.getBanishList().add(realizedTarget); //TODO we might encapsulate this a bit better; also not sure that a list of PC objects is really ideal + } + + } + } + } else { + if (guild.getGuildLeaderUUID() != target) { + PlayerCharacter toBanish = PlayerCharacter.getPlayerCharacter(target); + if (toBanish == null) + return true; + //already added previously. + if (SessionManager.getPlayerCharacterByID(toBanish.getObjectUUID()) != null) + return true; + + if (DbManager.GuildQueries.BANISH_FROM_GUILD_OFFLINE(target, GuildStatusController.isGuildLeader(source.getGuildStatus())) != 0) { + + success = true; + + //Set guild history + + if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), toBanish, DateTime.now(), GuildHistoryType.BANISHED)) { + GuildHistory guildHistory = new GuildHistory(toBanish.getGuildUUID(), toBanish.getGuild().getName(), DateTime.now(), GuildHistoryType.BANISHED); + toBanish.getGuildHistory().add(guildHistory); + } + } + } + } + + + if (success) { + //TODO re enable this once we get unbanish working!!!! + //DbManager.GuildQueries.ADD_TO_BANISHED_FROM_GUILDLIST(guild.getobjectUUID(), target); + + // Send left guild message to rest of guild + String targetName = PlayerCharacter.getFirstName(target); + ChatManager.chatGuildInfo(guild, + targetName + " has been banished from " + guild.getName() + '.'); + GuildListMsg guildListMsg = new GuildListMsg(guild); + dispatch = Dispatch.borrow(source, guildListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } else { + ErrorPopupMsg.sendErrorPopup(source, 103); // You may not banish this char + } + return true; + + } + +} diff --git a/src/engine/net/client/handlers/BreakFealtyHandler.java b/src/engine/net/client/handlers/BreakFealtyHandler.java new file mode 100644 index 00000000..c713d7d6 --- /dev/null +++ b/src/engine/net/client/handlers/BreakFealtyHandler.java @@ -0,0 +1,190 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.BreakFealtyMsg; +import engine.net.client.msg.guild.SendGuildEntryMsg; +import engine.objects.*; +import engine.server.world.WorldServer; +import engine.session.Session; + +import java.util.ArrayList; + +public class BreakFealtyHandler extends AbstractClientMsgHandler { + + public BreakFealtyHandler() { + super(BreakFealtyMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + BreakFealtyMsg bfm; + PlayerCharacter player; + Guild toBreak; + Guild guild; + Dispatch dispatch; + + bfm = (BreakFealtyMsg) baseMsg; + + // get PlayerCharacter of person accepting invite + + player = SessionManager.getPlayerCharacter( + origin); + + if (player == null) + return true; + + toBreak = (Guild) DbManager.getObject(GameObjectType.Guild, bfm.getGuildUUID()); + + if (toBreak == null) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return true; + } + + guild = player.getGuild(); + + if (guild == null) { + ErrorPopupMsg.sendErrorMsg(player, "You do not belong to a guild!"); + return true; + } + + if (toBreak.isNPCGuild()) { + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(player, "Only guild leader can break fealty!"); + return true; + } + + + if (!DbManager.GuildQueries.UPDATE_PARENT(guild.getObjectUUID(), WorldServer.worldUUID)) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return true; + } + + switch (guild.getGuildState()) { + case Sworn: + guild.setNation(null); + GuildManager.updateAllGuildTags(guild); + GuildManager.updateAllGuildBinds(guild, null); + break; + case Province: + guild.setNation(guild); + GuildManager.updateAllGuildTags(guild); + GuildManager.updateAllGuildBinds(guild, guild.getOwnedCity()); + break; + } + + guild.downgradeGuildState(); + + SendGuildEntryMsg msg = new SendGuildEntryMsg(player); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + //Update Map. + + final Session s = SessionManager.getSession(player); + + City.lastCityUpdate = System.currentTimeMillis(); + + + ArrayList guildMembers = SessionManager.getActivePCsInGuildID(guild.getObjectUUID()); + + for (PlayerCharacter member : guildMembers) { + ChatManager.chatGuildInfo(member, guild.getName() + " has broke fealty from " + toBreak.getName() + '!'); + } + + ArrayList breakFealtyMembers = SessionManager.getActivePCsInGuildID(toBreak.getObjectUUID()); + + for (PlayerCharacter member : breakFealtyMembers) { + ChatManager.chatGuildInfo(member, guild.getName() + " has broken fealty from " + toBreak.getName() + '!'); + } + + return true; + + + } + + if (!toBreak.getSubGuildList().contains(guild)) { + ErrorPopupMsg.sendErrorMsg(player, "Failure to break fealty!"); + return true; + } + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(player, "Only guild leader can break fealty!"); + return true; + } + + if (Bane.getBaneByAttackerGuild(guild) != null) { + ErrorPopupMsg.sendErrorMsg(player, "You may break fealty with active bane!"); + return true; + } + + if (!DbManager.GuildQueries.UPDATE_PARENT(guild.getObjectUUID(), WorldServer.worldUUID)) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return true; + } + + switch (guild.getGuildState()) { + case Sworn: + guild.setNation(null); + GuildManager.updateAllGuildTags(guild); + GuildManager.updateAllGuildBinds(guild, null); + break; + case Province: + guild.setNation(guild); + GuildManager.updateAllGuildTags(guild); + GuildManager.updateAllGuildBinds(guild, guild.getOwnedCity()); + break; + } + + guild.downgradeGuildState(); + toBreak.getSubGuildList().remove(guild); + + if (toBreak.getSubGuildList().isEmpty()) + toBreak.downgradeGuildState(); + + SendGuildEntryMsg msg = new SendGuildEntryMsg(player); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + //Update Map. + + final Session s = SessionManager.getSession(player); + + City.lastCityUpdate = System.currentTimeMillis(); + + + ArrayList guildMembers = SessionManager.getActivePCsInGuildID(guild.getObjectUUID()); + + for (PlayerCharacter member : guildMembers) { + ChatManager.chatGuildInfo(member, guild.getName() + " has broke fealty from " + toBreak.getName() + '!'); + } + + ArrayList breakFealtyMembers = SessionManager.getActivePCsInGuildID(toBreak.getObjectUUID()); + + for (PlayerCharacter member : breakFealtyMembers) { + ChatManager.chatGuildInfo(member, guild.getName() + " has broken fealty from " + toBreak.getName() + '!'); + } + + return true; + } +} diff --git a/src/engine/net/client/handlers/ChangeAltitudeHandler.java b/src/engine/net/client/handlers/ChangeAltitudeHandler.java new file mode 100644 index 00000000..cdc2ea99 --- /dev/null +++ b/src/engine/net/client/handlers/ChangeAltitudeHandler.java @@ -0,0 +1,180 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ChangeAltitudeMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.AbstractCharacter; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import engine.objects.Regions; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +public class ChangeAltitudeHandler extends AbstractClientMsgHandler { + + public ChangeAltitudeHandler() { + super(ChangeAltitudeMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + ChangeAltitudeMsg msg = (ChangeAltitudeMsg) baseMsg; + + PlayerCharacter pc = origin.getPlayerCharacter(); + if (pc == null) { + return false; + } + + if (!AbstractCharacter.CanFly(pc)) + return false; + + if (pc.isSwimming()) + return false; + if (pc.region != null && !pc.region.isOutside()) + return false; + + + // Find out if we already have an altitude timer running and if so + // do not process more alt change requests + + if (pc.getTakeOffTime() != 0) + return false; + + + // remove all movement timers and jobs + //TODO: test if they can fly + + float targetAlt; + float amount = msg.getAmountToMove(); + if (amount != 10 && amount != 60) + return false; + if (pc.getAltitude() == 60 && msg.up()) + return true; + if (pc.getAltitude() == 0 && !msg.up()) + return true; + + pc.update(); + pc.stopMovement(pc.getLoc()); + msg.setStartAlt(pc.getAltitude()); + if (msg.up()) { + + pc.landingRegion = null; + if (pc.getAltitude() == 0) { + Regions upRegion = pc.region; + if (upRegion != null) { + float startAlt = 0; + Building regionBuilding = Regions.GetBuildingForRegion(upRegion); + if (upRegion != null) + startAlt = upRegion.lerpY(pc) - regionBuilding.getLoc().y; + float rounded = startAlt * .10f; + + rounded = ((int) rounded) * 10; + + if (rounded < 0) + rounded = 0; + + + msg.setStartAlt(startAlt); + targetAlt = rounded + amount; + + if (targetAlt > 60) + targetAlt = 60; + + pc.setAltitude(startAlt); + pc.setDesiredAltitude(targetAlt); + } else { + msg.setStartAlt(pc.getAltitude()); + targetAlt = pc.getAltitude() + amount; + if (targetAlt > 60) + targetAlt = 60; + } + } else { + msg.setStartAlt(pc.getAltitude()); + + targetAlt = pc.getAltitude() + amount; + if (targetAlt > 60) + targetAlt = 60; + } + + + } else { + msg.setStartAlt(pc.getAltitude()); + targetAlt = pc.getAltitude() - amount; + if (targetAlt < 0) + targetAlt = 0; + } + msg.setTargetAlt(targetAlt); + if (pc.getAltitude() < 1 && targetAlt > pc.getAltitude()) { + // char is on the ground and is about to start flight + if (pc.getStamina() < 10) { + return false; + } + } + + if (MBServerStatics.MOVEMENT_SYNC_DEBUG) { + Logger.info("Changing Altitude, moving=" + pc.isMoving() + + " Current Loc " + pc.getLoc().getX() + ' ' + pc.getLoc().getZ() + + " End Loc " + pc.getEndLoc().getX() + ' ' + pc.getEndLoc().getZ()); + } + + if (msg.up()) { + pc.update(); + pc.setDesiredAltitude(targetAlt); + pc.setTakeOffTime(System.currentTimeMillis()); + } else { + Regions region = PlayerCharacter.InsideBuildingRegionGoingDown(pc); + + + if (region != null) { + float landingAltitude = 0; + Building building = Regions.GetBuildingForRegion(region); + if (building != null) + landingAltitude = region.lerpY(pc) - building.getLoc().y; + + if (landingAltitude >= targetAlt) { + pc.landingRegion = region; + pc.landingAltitude = landingAltitude; + pc.setDesiredAltitude(landingAltitude); + } else { + pc.landingRegion = null; + pc.setDesiredAltitude(targetAlt); + } + + + } else + pc.setDesiredAltitude(targetAlt); + + pc.update(); + + + pc.setTakeOffTime(System.currentTimeMillis()); + } + + + // Add timer for height change cooldown, this also tells getLoc we are not moving + //MovementManager.addChangeAltitudeTimer(pc, msg.getStartAlt(), msg.getTargetAlt(), (int)((MBServerStatics.HEIGHT_CHANGE_TIMER_MS * amount) + 100 ) ); + + // Add flight timer job to check stam and ground you when you run out + //MovementManager.addFlightTimer(pc, msg, MBServerStatics.FLY_FREQUENCY_MS); + //Send change altitude to all in range + DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + + return true; + } + +} diff --git a/src/engine/net/client/handlers/ChangeGuildLeaderHandler.java b/src/engine/net/client/handlers/ChangeGuildLeaderHandler.java new file mode 100644 index 00000000..fe21996b --- /dev/null +++ b/src/engine/net/client/handlers/ChangeGuildLeaderHandler.java @@ -0,0 +1,140 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ChangeGuildLeaderMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.net.client.msg.guild.GuildListMsg; +import engine.objects.City; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class ChangeGuildLeaderHandler extends AbstractClientMsgHandler { + + public ChangeGuildLeaderHandler() { + super(ChangeGuildLeaderMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + ChangeGuildLeaderMsg msg; + PlayerCharacter sourcePlayer; + PlayerCharacter targetPlayer; + City city; + + msg = (ChangeGuildLeaderMsg) baseMsg; + sourcePlayer = origin.getPlayerCharacter(); + if (sourcePlayer == null) + return true; + if (GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus()) == false) + return true; + + + Guild glGuild = sourcePlayer.getGuild(); + + if (glGuild == null) + return true; + + if (!glGuild.isGuildLeader(sourcePlayer.getObjectUUID())) + return true; + targetPlayer = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getTargetID()); + + + if (targetPlayer == null) + return true; + + if (GuildStatusController.isGuildLeader(targetPlayer.getGuildStatus())) + return true; + + if (!Guild.sameGuild(sourcePlayer.getGuild(), targetPlayer.getGuild())) + return false; + + + //updateSource will generate a new promote/demote screen for sourcePlayer + //updateTarget will sync guild info for the target and all players in range + + + String targetName = null; + boolean isMale = true; + boolean updateTarget; + + Enum.GuildType t = Enum.GuildType.getGuildTypeFromInt(sourcePlayer.getGuild().getCharter()); + + + if (!DbManager.GuildQueries.SET_GUILD_LEADER(targetPlayer.getObjectUUID(), glGuild.getObjectUUID())) { + ChatManager.chatGuildError(sourcePlayer, "Failed to change guild leader!"); + return false; + } + + glGuild.setGuildLeader(targetPlayer); + + + if (glGuild.getOwnedCity() != null) { + city = glGuild.getOwnedCity(); + if (!city.transferGuildLeader(targetPlayer)) { + ChatManager.chatGuildError(sourcePlayer, "Failed to Transfer City Objects. Contact CCR!"); + return false; + } + + } + + + targetPlayer.setGuildLeader(true); + targetPlayer.setInnerCouncil(true); + targetPlayer.setFullMember(true); + targetPlayer.incVer(); + targetName = targetPlayer.getFirstName(); + updateTarget = true; + + + ChatManager.chatGuildInfo(sourcePlayer.getGuild(), + targetName + " has been promoted to " + + "guild leader!"); + + //These values record a change, not the new value... + + + //updateOldGuildLeader + sourcePlayer.setInnerCouncil(true); + sourcePlayer.setFullMember(true); + sourcePlayer.setGuildLeader(false); + sourcePlayer.incVer(); + + GuildInfoMsg guildInfoMsg = new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 2); + Dispatch dispatch = Dispatch.borrow(sourcePlayer, guildInfoMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + GuildListMsg guildListMsg = new GuildListMsg(sourcePlayer.getGuild()); + + dispatch = Dispatch.borrow(sourcePlayer, guildListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + + if (targetPlayer != null) { + if (updateTarget) + DispatchMessage.sendToAllInRange(targetPlayer, new GuildInfoMsg(targetPlayer, targetPlayer.getGuild(), 2)); + } + + return true; + + } + +} diff --git a/src/engine/net/client/handlers/ChangeRankHandler.java b/src/engine/net/client/handlers/ChangeRankHandler.java new file mode 100644 index 00000000..8197fbff --- /dev/null +++ b/src/engine/net/client/handlers/ChangeRankHandler.java @@ -0,0 +1,164 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.ChangeRankMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.net.client.msg.guild.GuildListMsg; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class ChangeRankHandler extends AbstractClientMsgHandler { + + public ChangeRankHandler() { + super(ChangeRankMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + ChangeRankMsg msg; + PlayerCharacter sourcePlayer; + PlayerCharacter targetPlayer; + + msg = (ChangeRankMsg) baseMsg; + sourcePlayer = origin.getPlayerCharacter(); + + targetPlayer = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getPlayerUUID()); + + if (msg.getPlayerUUID() == 0) + targetPlayer = sourcePlayer; + + //updateSource will generate a new promote/demote screen for sourcePlayer + //updateTarget will sync guild info for the target and all players in range + + boolean updateSource = false, updateTarget = false; + + if (sourcePlayer == null || GuildStatusController.isInnerCouncil(sourcePlayer.getGuildStatus()) == false) + return true; + + if (targetPlayer != null && (targetPlayer.getGuild().equals(sourcePlayer.getGuild()) == false)) + return true; + + String targetName = null; + boolean isMale; + + if (msg.getPreviousRank() != msg.getNewRank()) { + Enum.GuildType t = Enum.GuildType.getGuildTypeFromInt(sourcePlayer.getGuild().getCharter()); + + if (targetPlayer != null) { + targetPlayer.setGuildTitle(msg.getNewRank()); + + targetName = targetPlayer.getFirstName(); + isMale = targetPlayer.getRace().getRaceType().getCharacterSex().equals(Enum.CharacterSex.MALE); + } else { + DbManager.GuildQueries.UPDATE_GUILD_RANK_OFFLINE(msg.getPlayerUUID(), msg.getNewRank(), sourcePlayer.getGuild().getObjectUUID()); + + targetName = PlayerCharacter.getFirstName(msg.getPlayerUUID()); + isMale = true; + } + + ChatManager.chatGuildInfo(sourcePlayer.getGuild(), + targetName + " has been " + + ((msg.getNewRank() > msg.getPreviousRank()) ? "pro" : "de") + "moted to " + + t.getRankForGender(msg.getNewRank(), isMale) + '!'); + updateSource = true; + } + + //These values record a change, not the new value... + boolean icUpdate = false, recruitUpdate = false, taxUpdate = false; + + //Handle the offline case.. + if (targetPlayer == null) { + int updateMask = DbManager.GuildQueries.UPDATE_GUILD_STATUS_OFFLINE(msg.getPlayerUUID(), + msg.getIc() > 0, + msg.getRec() > 0, + msg.getTax() > 0, + sourcePlayer.getGuild().getObjectUUID()); + + //These values come from the updateIsStatusOffline function + icUpdate = (updateMask & 4) > 0; + recruitUpdate = (updateMask & 2) > 0; + taxUpdate = (updateMask & 1) > 0; + + if (targetName == null && updateMask > 0) + targetName = PlayerCharacter.getFirstName(msg.getPlayerUUID()); + } else { + icUpdate = (GuildStatusController.isInnerCouncil(targetPlayer.getGuildStatus()) != (msg.getIc() > 0)) && GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus()); + recruitUpdate = (GuildStatusController.isRecruiter(targetPlayer.getGuildStatus()) != (msg.getRec() > 0)) && GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus()); + taxUpdate = (GuildStatusController.isTaxCollector(targetPlayer.getGuildStatus()) != (msg.getTax() > 0)) && GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus()); + + //This logic branch only executes if targetPlayer has passed a null check... + if (icUpdate) { + targetPlayer.setInnerCouncil(msg.getIc() > 0); + targetPlayer.setFullMember(true); + targetPlayer.incVer(); + } + + if (recruitUpdate) + targetPlayer.setRecruiter(msg.getRec() > 0); + + if (taxUpdate) + targetPlayer.setTaxCollector(msg.getTax() > 0); + + if (targetName == null) + targetName = targetPlayer.getFirstName(); + } + + if (icUpdate) { + ChatManager.chatGuildInfo(sourcePlayer.getGuild(), + (msg.getIc() > 0) + ? targetName + " has been appointed to the inner council." + : targetName + " is no longer a member of the inner council."); + + updateSource = true; + updateTarget = true; + } + + if (recruitUpdate) { + updateSource = true; + updateTarget = true; + } + + if (taxUpdate) { + updateSource = true; + updateTarget = true; + } + + if (targetPlayer != null) { + targetPlayer.incVer(); + if (updateTarget) + DispatchMessage.sendToAllInRange(targetPlayer, new GuildInfoMsg(targetPlayer, targetPlayer.getGuild(), 2)); + } + + if (updateSource) { + + Dispatch dispatch = Dispatch.borrow(sourcePlayer, new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 2)); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + dispatch = Dispatch.borrow(sourcePlayer, new GuildListMsg(sourcePlayer.getGuild())); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + } + + return true; + + } + +} diff --git a/src/engine/net/client/handlers/ChannelMuteMsgHandler.java b/src/engine/net/client/handlers/ChannelMuteMsgHandler.java new file mode 100644 index 00000000..71d40686 --- /dev/null +++ b/src/engine/net/client/handlers/ChannelMuteMsgHandler.java @@ -0,0 +1,123 @@ +package engine.net.client.handlers; + +import engine.Enum.BuildingGroup; +import engine.Enum.GuildState; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.ZoneManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ChatFilterMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which processes + * client requests to abandon a building. + */ +public class ChannelMuteMsgHandler extends AbstractClientMsgHandler { + + // Instance variables + + public ChannelMuteMsgHandler() { + super(ChatFilterMsg.class); + + } + + private static void AbandonSingleAsset(PlayerCharacter sourcePlayer, + Building targetBuilding) { + + // Transfer the building asset ownership and refresh all clients + + DbManager.BuildingQueries.CLEAR_FRIENDS_LIST(targetBuilding.getObjectUUID()); + targetBuilding.getFriends().clear(); + + DbManager.BuildingQueries.CLEAR_CONDEMNED_LIST(targetBuilding.getObjectUUID()); + targetBuilding.getCondemned().clear(); + targetBuilding.setOwner(null); + targetBuilding.refreshGuild(); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + return true; + } + + private void AbandonAllCityObjects(PlayerCharacter sourcePlayer, + Building targetBuilding) { + Guild sourceGuild; + Zone cityZone; + + sourceGuild = sourcePlayer.getGuild(); + + if (sourceGuild == null) + return; + + if (sourceGuild.getSubGuildList().size() > 0) { + ChatManager.chatCityError(sourcePlayer, "You Cannot abandon a nation city."); + return; + } + + cityZone = ZoneManager.findSmallestZone(targetBuilding.getLoc()); + + // Can't abandon a tree not within a player city zone + if (cityZone.isPlayerCity() == false) + return; + + if (targetBuilding.getCity().hasBeenTransfered == true) { + ChatManager.chatCityError(sourcePlayer, "City can only be abandoned once per rebooting."); + return; + } + + // Guild no longer owns his tree. + if (!DbManager.GuildQueries.SET_GUILD_OWNED_CITY(sourceGuild.getObjectUUID(), 0)) { + Logger.error("Failed to update Owned City to Database"); + return; + } + + sourceGuild.setCityUUID(0); + sourceGuild.setGuildState(GuildState.Errant); + sourceGuild.setNation(null); + + // Transfer the city assets + TransferCityAssets(sourcePlayer, targetBuilding); + + GuildManager.updateAllGuildTags(sourceGuild); + GuildManager.updateAllGuildBinds(sourceGuild, null); + + } + + private void TransferCityAssets(PlayerCharacter sourcePlayer, + Building cityTOL) { + + Zone cityZone; + + // Build list of buildings within this parent zone + cityZone = ZoneManager.findSmallestZone(cityTOL.getLoc()); + + for (Building cityBuilding : cityZone.zoneBuildingSet) { + + Blueprint cityBlueprint; + cityBlueprint = cityBuilding.getBlueprint(); + + // Buildings without blueprints cannot be abandoned + if (cityBlueprint == null) + continue; + + // Transfer ownership of valid city assets + if ((cityBlueprint.getBuildingGroup() == BuildingGroup.TOL) + || (cityBlueprint.getBuildingGroup() == BuildingGroup.SPIRE) + || (cityBlueprint.getBuildingGroup() == BuildingGroup.BARRACK) + || (cityBlueprint.isWallPiece()) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) + AbandonSingleAsset(sourcePlayer, cityBuilding); + + } + + } + +} diff --git a/src/engine/net/client/handlers/CityChoiceMsgHandler.java b/src/engine/net/client/handlers/CityChoiceMsgHandler.java new file mode 100644 index 00000000..32b5686c --- /dev/null +++ b/src/engine/net/client/handlers/CityChoiceMsgHandler.java @@ -0,0 +1,69 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.GuildManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.CityChoiceMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.TeleportRepledgeListMsg; +import engine.objects.City; +import engine.objects.Guild; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ + +public class CityChoiceMsgHandler extends AbstractClientMsgHandler { + + public CityChoiceMsgHandler() { + super(CityChoiceMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + CityChoiceMsg msg = (CityChoiceMsg) baseMsg; + + if (player == null) + return true; + + switch (msg.getMsgType()) { + case 5: + TeleportRepledgeListMsg trlm = new TeleportRepledgeListMsg(player, false); + trlm.configure(); + dispatch = Dispatch.borrow(player, trlm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + break; + case 3: + City city = City.getCity(msg.getCityID()); + + if (city == null) + return true; + + Guild cityGuild = city.getGuild(); + + if (cityGuild == null) + return true; + + if (player.getLevel() < cityGuild.getRepledgeMin() || player.getLevel() > cityGuild.getRepledgeMax()) + return true; + + //if repledge, reguild the player but set his building now. + + GuildManager.joinGuild(player, cityGuild, city.getObjectUUID(), Enum.GuildHistoryType.JOIN); + break; + } + + return true; + } +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/CityDataHandler.java b/src/engine/net/client/handlers/CityDataHandler.java new file mode 100644 index 00000000..98ca60fb --- /dev/null +++ b/src/engine/net/client/handlers/CityDataHandler.java @@ -0,0 +1,82 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.gameManager.ZoneManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.City; +import engine.objects.PlayerCharacter; +import engine.session.Session; + +/* + * @Author: + * @Summary: Processes application protocol message which displays + * the map interface. (Zones, Cities, Realms, Hot-zones) + */ + +public class CityDataHandler extends AbstractClientMsgHandler { + + public CityDataHandler() { + super(KeepAliveServerClientMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + boolean updateCities = false; + Session playerSession; + PlayerCharacter playerCharacter; + Dispatch dispatch; + + playerCharacter = origin.getPlayerCharacter(); + + if (playerCharacter == null) + return true; + + // Session is needed as param for worldObjectMsg. + + playerSession = SessionManager.getSession(playerCharacter); + + if (playerSession == null) + return true; + + // No reason to serialize cities everytime map is + // opened. Wait until something has changed. + // This does not work for mines. + + if (playerCharacter.getTimeStamp("cityUpdate") <= City.lastCityUpdate) { + playerCharacter.setTimeStamp("cityUpdate", System.currentTimeMillis()); + updateCities = true; + } + + CityDataMsg cityDataMsg = new CityDataMsg(SessionManager.getSession(playerCharacter), false); + cityDataMsg.updateMines(true); + cityDataMsg.updateCities(updateCities); + + dispatch = Dispatch.borrow(playerCharacter, cityDataMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + // If the hotZone has changed then update the client's map accordingly. + + if (playerCharacter.getTimeStamp("hotzoneupdate") <= ZoneManager.hotZoneLastUpdate.toEpochMilli() && ZoneManager.hotZone != null) { + HotzoneChangeMsg hotzoneChangeMsg = new HotzoneChangeMsg(Enum.GameObjectType.Zone.ordinal(), ZoneManager.hotZone.getObjectUUID()); + dispatch = Dispatch.borrow(playerCharacter, hotzoneChangeMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + playerCharacter.setTimeStamp("hotzoneupdate", System.currentTimeMillis() - 100); + } + + // Serialize the realms for this map + + WorldRealmMsg worldRealmMsg = new WorldRealmMsg(); + dispatch = Dispatch.borrow(playerCharacter, worldRealmMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/ClaimAssetMsgHandler.java b/src/engine/net/client/handlers/ClaimAssetMsgHandler.java new file mode 100644 index 00000000..4d1cd6d0 --- /dev/null +++ b/src/engine/net/client/handlers/ClaimAssetMsgHandler.java @@ -0,0 +1,139 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.db.archive.CityRecord; +import engine.db.archive.DataWarehouse; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClaimAssetMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Blueprint; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static engine.math.FastMath.sqr; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ +public class ClaimAssetMsgHandler extends AbstractClientMsgHandler { + + // Instance variables + + private final ReentrantReadWriteLock claimLock = new ReentrantReadWriteLock(); + + public ClaimAssetMsgHandler() { + super(ClaimAssetMsg.class); + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + this.claimLock.writeLock().lock(); + + try { + PlayerCharacter sourcePlayer; + Building targetBuilding; + Blueprint blueprint; + ClaimAssetMsg msg; + int targetUUID; + + msg = (ClaimAssetMsg) baseMsg; + targetUUID = msg.getUUID(); + + sourcePlayer = origin.getPlayerCharacter(); + targetBuilding = BuildingManager.getBuildingFromCache(targetUUID); + + // Oops! *** Refactor: Log error + + if ((sourcePlayer == null) || + (targetBuilding == null)) + return true; + + // Player must be reasonably close to building in order to claim + + if (sourcePlayer.getLoc().distanceSquared2D(targetBuilding.getLoc()) > sqr(100)) + return true; + + // Early exit if object to be claimed is not errant + + if (targetBuilding.getOwnerUUID() != 0) + return true; + + + // Early exit if UUID < the last database derived building UUID. + + if (targetBuilding.getProtectionState() == Enum.ProtectionState.NPC) { + return true; + } + + // Early exit if claiming player does not + // have a guild. + + // Errant players cannot claim + + if (sourcePlayer.getGuild().isEmptyGuild()) + return true; + + // Can't claim an object without a blueprint + + if (targetBuilding.getBlueprintUUID() == 0) + return true; + + blueprint = targetBuilding.getBlueprint(); + + //cant claim mine this way. + if (blueprint.getBuildingGroup() == BuildingGroup.MINE) + return true; + + // Players cannot claim shrines + + if ((targetBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) + return true; + + // Can't claim a tree if your guild already owns one + // *** Refactor : Send error to player here + + if ((sourcePlayer.getGuild().isNation()) && + (blueprint.getBuildingGroup() == BuildingGroup.TOL)) + return true; + + // Process the transfer of the building(s) + + if (blueprint.getBuildingGroup() == BuildingGroup.TOL) { + targetBuilding.getCity().claim(sourcePlayer); + + // Push transfer of city to data warehouse + CityRecord cityRecord = CityRecord.borrow(targetBuilding.getCity(), Enum.RecordEventType.TRANSFER); + DataWarehouse.pushToWarehouse(cityRecord); + + } else + targetBuilding.claim(sourcePlayer); + + } catch (Exception e) { + Logger.error("ClaimAssetMsgHandler", e.getMessage()); + } finally { + this.claimLock.writeLock().unlock(); + } + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/ClaimGuildTreeMsgHandler.java b/src/engine/net/client/handlers/ClaimGuildTreeMsgHandler.java new file mode 100644 index 00000000..dad056fd --- /dev/null +++ b/src/engine/net/client/handlers/ClaimGuildTreeMsgHandler.java @@ -0,0 +1,191 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ +public class ClaimGuildTreeMsgHandler extends AbstractClientMsgHandler { + + // Instance variables + + private static final int RENAME_TREE = 2; + private static final int BIND_TREE = 3; + private static final int OPEN_CITY = 4; + private static final int CLOSE_CITY = 5; + private final ReentrantReadWriteLock claimLock = new ReentrantReadWriteLock(); + + public ClaimGuildTreeMsgHandler() { + super(ClaimGuildTreeMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + this.claimLock.writeLock().lock(); + + try { + PlayerCharacter sourcePlayer; + Building building; + Blueprint blueprint; + Zone playerZone = null; + City playerCity = null; + ClaimGuildTreeMsg msg; + int targetUUID; + Dispatch dispatch; + + msg = (ClaimGuildTreeMsg) baseMsg; + targetUUID = msg.getTargetID(); + + sourcePlayer = origin.getPlayerCharacter(); + building = BuildingManager.getBuildingFromCache(targetUUID); + + if (building != null) + playerZone = building.getParentZone(); + + if (playerZone != null) + playerCity = City.getCity(playerZone.getPlayerCityUUID()); + + // Oops! *** Refactor: Log error + switch (msg.getMessageType()) { + case RENAME_TREE: + if ((sourcePlayer == null) || + (building == null) || playerZone == null || playerCity == null) + return true; + + // Early exit if object to be claimed is not errant + + if (building.getOwnerUUID() == 0) + return true; + + // Errant players cannot rename + + if (sourcePlayer.getGuild().isEmptyGuild()) + return true; + + // Can't rename an object without a blueprint + + if (building.getBlueprintUUID() == 0) + return true; + + blueprint = building.getBlueprint(); + + //can only rename tree this way. + if (blueprint.getBuildingGroup() != BuildingGroup.TOL) + return true; + + //dont rename if guild is null + if (building.getGuild().isEmptyGuild()) + return true; + + if (!ManageCityAssetMsgHandler.playerCanManageNotFriends(sourcePlayer, building)) + return true; + + + if (!playerCity.renameCity(msg.getTreeName())) { + ChatManager.chatSystemError(sourcePlayer, "Failed to rename city!"); + return true; + } + + GuildTreeStatusMsg gtsm = new GuildTreeStatusMsg(building, sourcePlayer); + gtsm.configure(); + dispatch = Dispatch.borrow(sourcePlayer, gtsm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + CityZoneMsg czm = new CityZoneMsg(2, playerZone.getLoc().x, playerZone.getLoc().y, playerZone.getLoc().z, playerCity.getCityName(), playerZone, Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents); + DispatchMessage.dispatchMsgToAll(czm); + + break; + case BIND_TREE: + + Guild pcGuild = sourcePlayer.getGuild(); + + //test tree is valid for binding, same guild or same nation + if (!Guild.sameNation(pcGuild, building.getGuild())) { + + return true; + } + + if (building.getGuild().isEmptyGuild()) + return true; + + + //get bind city + Zone zone = building.getParentZone(); + + if (zone == null) { + ErrorPopupMsg.sendErrorMsg(sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return true; + } + + if (playerCity == null && building.getGuild() != null) + playerCity = building.getGuild().getOwnedCity(); + + if (playerCity == null) + return true; + + + sourcePlayer.setBindBuildingID(building.getObjectUUID()); + dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + case OPEN_CITY: + case CLOSE_CITY: + if ((sourcePlayer == null) || + (building == null) || playerZone == null || playerCity == null) + return true; + + if (!ManageCityAssetMsgHandler.playerCanManageNotFriends(sourcePlayer, building)) + return true; + + boolean open = (msg.getMessageType() == OPEN_CITY); + + if (!playerCity.openCity(open)) { + ErrorPopupMsg.sendErrorMsg(sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return true; + } + + dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + default: + break; + } + + } catch (Exception e) { + Logger.error(e.getMessage()); + } finally { + try { + this.claimLock.writeLock().unlock(); + } catch (Exception e) { + Logger.info("failClaimsync"); + } + } + return true; + } +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/DestroyBuildingHandler.java b/src/engine/net/client/handlers/DestroyBuildingHandler.java new file mode 100644 index 00000000..18ce4d05 --- /dev/null +++ b/src/engine/net/client/handlers/DestroyBuildingHandler.java @@ -0,0 +1,108 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.InterestManagement.WorldGrid; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.DestroyBuildingMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.*; + +/* + * @Author: + * @Summary: Processes application protocol message where the + * client is requesting a building be destroyed. + */ + +public class DestroyBuildingHandler extends AbstractClientMsgHandler { + + public DestroyBuildingHandler() { + super(DestroyBuildingMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter pc = origin.getPlayerCharacter(); + + DestroyBuildingMsg msg; + msg = (DestroyBuildingMsg) baseMsg; + Bane bane = null; + Blueprint blueprint; + + int buildingUUID = msg.getUUID(); + + Building building = BuildingManager.getBuildingFromCache(buildingUUID); + ; + if (pc == null || building == null) + return true; + + blueprint = building.getBlueprint(); + City city = building.getCity(); + + // Can't destroy buildings without a blueprint. + + if (blueprint == null) + return true; + + // Cannot destroy Oblivion database derived buildings. + + if (building.getProtectionState() == Enum.ProtectionState.NPC) { + return true; + } + + if (!BuildingManager.PlayerCanControlNotOwner(building, pc)) + return true; + + // Can't delete siege assets during an active bane. + + if (city != null) + bane = city.getBane(); + + if (bane != null && bane.getSiegePhase() == Enum.SiegePhase.WAR) { + ErrorPopupMsg.sendErrorPopup(pc, 171); + return true; + } + + // Can't destroy a tree of life + if (blueprint.getBuildingGroup() == BuildingGroup.TOL) + return true; + + // Can't destroy a shrine + if (blueprint.getBuildingGroup() == BuildingGroup.SHRINE) + return true; + + // Cannot destroy mines outside of normal mine mechanics + + if (blueprint.getBuildingGroup() == BuildingGroup.MINE) + return true; + + // Cannot delete rungates + + if (blueprint.getBuildingGroup() == BuildingGroup.RUNEGATE) + return true; + + + // Turn off spire if destoying + if (blueprint.getBuildingGroup() == BuildingGroup.SPIRE) + building.disableSpire(true); + + if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) { + if (city != null) + city.setWarehouseBuildingID(0); + } + + building.setRank(-1); + WorldGrid.RemoveWorldObject(building); + WorldGrid.removeObject(building); + building.getParentZone().zoneBuildingSet.remove(building); + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) { + building.RemoveFromBarracksList(); + } + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/DisbandGroupHandler.java b/src/engine/net/client/handlers/DisbandGroupHandler.java new file mode 100644 index 00000000..7b0ca472 --- /dev/null +++ b/src/engine/net/client/handlers/DisbandGroupHandler.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.GroupManager; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.DisbandGroupMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +public class DisbandGroupHandler extends AbstractClientMsgHandler { + + public DisbandGroupHandler() { + super(DisbandGroupMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter source = SessionManager.getPlayerCharacter(origin); + if (source == null) { + return false; + } + Group group = GroupManager.getGroup(source); + if (group == null) { + return false; + } + if (group.getGroupLead() != source) { + return false; + } + + // Clear all group member lists + GroupUpdateMsg gim = new GroupUpdateMsg(); + gim.setGroup(group); + gim.setMessageType(4); + + // send the disbanded popup to everyone in group + group.sendUpdate(gim); + + // cleanup group + GroupManager.deleteGroup(group); + return true; + + } +} diff --git a/src/engine/net/client/handlers/DisbandGuildHandler.java b/src/engine/net/client/handlers/DisbandGuildHandler.java new file mode 100644 index 00000000..15e1fbd8 --- /dev/null +++ b/src/engine/net/client/handlers/DisbandGuildHandler.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GuildHistoryType; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.DataWarehouse; +import engine.db.archive.GuildRecord; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.DisbandGuildMsg; +import engine.net.client.msg.guild.LeaveGuildMsg; +import engine.objects.Bane; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class DisbandGuildHandler extends AbstractClientMsgHandler { + + public DisbandGuildHandler() { + super(DisbandGuildMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + Guild guild; + Dispatch dispatch; + + player = SessionManager.getPlayerCharacter(origin); + + //don't allow non guild leaders to disband guild. + + if (player == null || GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) + return true; + + guild = player.getGuild(); + + if (guild == null || guild.isEmptyGuild()) + return true; + + // Don't allow disbanding if a city is owned + // *** Refactor: We should allow this by abandoning the tree first. + + if (guild.getOwnedCity() != null) { + ErrorPopupMsg.sendErrorMsg(player, "You cannot disband a soverign guild!"); + return true; + } + + Bane guildBane = Bane.getBaneByAttackerGuild(guild); + + if (guildBane != null) { + ErrorPopupMsg.sendErrorMsg(player, "You cannot disband a guild with an active bane!"); + return true; + } + + if (guild.getSubGuildList().size() > 0) { + ErrorPopupMsg.sendErrorMsg(player, "You cannot disband a nation!"); + return true; + } + + // Send message to guild (before kicking everyone out of it) + + ChatManager.chatGuildInfo(guild, guild.getName() + " has been disbanded!"); + + // Log event to data warehous + + GuildRecord guildRecord = GuildRecord.borrow(guild, Enum.RecordEventType.DISBAND); + DataWarehouse.pushToWarehouse(guildRecord); + + // Remove us as a subguild of our nation + + if (guild.getNation() != null && Guild.sameGuild(guild, guild.getNation()) == false && guild.getNation().isEmptyGuild() == false) + guild.getNation().removeSubGuild(guild); + + // Update all online guild players + + for (PlayerCharacter pcs : Guild.GuildRoster(guild)) { + + guild.removePlayer(pcs, GuildHistoryType.DISBAND); + } + + //Save Guild data + + player.setGuildLeader(false); + player.setInnerCouncil(false); + guild.setGuildLeaderUUID(0); + guild.setNation(null); + + DbManager.GuildQueries.DELETE_GUILD(guild); + + DbManager.removeFromCache(guild); + WorldGrid.removeObject(guild, player); + + // Send message back to client + + LeaveGuildMsg leaveGuildMsg = new LeaveGuildMsg(); + leaveGuildMsg.setMessage("You guild has been disbanded!"); + dispatch = Dispatch.borrow(player, leaveGuildMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/DismissGuildHandler.java b/src/engine/net/client/handlers/DismissGuildHandler.java new file mode 100644 index 00000000..a07054bf --- /dev/null +++ b/src/engine/net/client/handlers/DismissGuildHandler.java @@ -0,0 +1,146 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.DismissGuildMsg; +import engine.net.client.msg.guild.SendGuildEntryMsg; +import engine.objects.*; +import engine.server.world.WorldServer; +import engine.session.Session; + +import java.util.ArrayList; + +public class DismissGuildHandler extends AbstractClientMsgHandler { + + public DismissGuildHandler() { + super(DismissGuildMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + DismissGuildMsg dismissMsg; + PlayerCharacter player; + Guild toDismiss; + Guild nation; + Dispatch dispatch; + + dismissMsg = (DismissGuildMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + toDismiss = (Guild) DbManager.getObject(GameObjectType.Guild, dismissMsg.getGuildID()); + + if (toDismiss == null) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return true; + } + + nation = player.getGuild(); + + if (nation == null) { + ErrorPopupMsg.sendErrorMsg(player, "Nothing to disband, your guild is not a nation!"); + return true; + } + + if (!nation.getSubGuildList().contains(toDismiss)) { + ErrorPopupMsg.sendErrorMsg(player, "You do not have authority to dismiss this guild!"); + return true; + } + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(player, "Only a guild leader can dismiss a subguild!"); + return true; + } + + // Restriction on active bane desubbing + + if (Bane.getBaneByAttackerGuild(toDismiss) != null) { + ErrorPopupMsg.sendErrorMsg(player, "You may not dismiss subguild with active bane!"); + return true; + } + + switch (toDismiss.getGuildState()) { + case Sworn: + + if (!DbManager.GuildQueries.UPDATE_PARENT(toDismiss.getObjectUUID(), WorldServer.worldUUID)) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return true; + } + nation.getSubGuildList().remove(toDismiss); + toDismiss.downgradeGuildState(); + toDismiss.setNation(null); + GuildManager.updateAllGuildBinds(toDismiss, null); + + break; + case Province: + if (!DbManager.GuildQueries.UPDATE_PARENT(toDismiss.getObjectUUID(), WorldServer.worldUUID)) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return true; + } + nation.getSubGuildList().remove(toDismiss); + toDismiss.downgradeGuildState(); + toDismiss.setNation(toDismiss); + + break; + case Petitioner: + nation.getSubGuildList().remove(toDismiss); + toDismiss.downgradeGuildState(); + break; + case Protectorate: + nation.getSubGuildList().remove(toDismiss); + toDismiss.downgradeGuildState(); + break; + } + + GuildManager.updateAllGuildTags(toDismiss); + + if (nation.getSubGuildList().isEmpty()) + nation.downgradeGuildState(); + + SendGuildEntryMsg msg = new SendGuildEntryMsg(player); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + final Session s = SessionManager.getSession(player); + + City.lastCityUpdate = System.currentTimeMillis(); + + + ArrayList guildMembers = SessionManager.getActivePCsInGuildID(nation.getObjectUUID()); + + for (PlayerCharacter member : guildMembers) { + ChatManager.chatGuildInfo(member, toDismiss.getName() + " has been dismissed as a subguild!"); + } + + ArrayList dismissedMembers = SessionManager.getActivePCsInGuildID(toDismiss.getObjectUUID()); + + for (PlayerCharacter member : dismissedMembers) { + ChatManager.chatGuildInfo(member, nation.getName() + "has dismissed you as a sub!"); + } + + return true; + } +} diff --git a/src/engine/net/client/handlers/DoorTryOpenMsgHandler.java b/src/engine/net/client/handlers/DoorTryOpenMsgHandler.java new file mode 100644 index 00000000..990e974d --- /dev/null +++ b/src/engine/net/client/handlers/DoorTryOpenMsgHandler.java @@ -0,0 +1,88 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DoorState; +import engine.InterestManagement.WorldGrid; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.DoorTryOpenMsg; +import engine.objects.AbstractWorldObject; +import engine.objects.Blueprint; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.HashSet; + +/* + * @Author: + * @Summary: Processes application protocol message which handle + * open and close door requests to and from the client. + * + */ +public class DoorTryOpenMsgHandler extends AbstractClientMsgHandler { + + public DoorTryOpenMsgHandler() { + super(DoorTryOpenMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + DoorTryOpenMsg msg; + Building targetBuilding; + int doorNumber; + + // Member variable assignment + + msg = (DoorTryOpenMsg) baseMsg; + player = origin.getPlayerCharacter(); + targetBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingUUID()); + + if (player == null || targetBuilding == null) { + Logger.error("Player or Building returned NULL in OpenCloseDoor handling."); + return true; + } + + // Must be within x distance from door to manipulate it + + if (player.getLoc().distanceSquared2D(targetBuilding.getLoc()) > MBServerStatics.OPENCLOSEDOORDISTANCE * MBServerStatics.OPENCLOSEDOORDISTANCE) + return true; + + doorNumber = Blueprint.getDoorNumberbyMesh(msg.getDoorID()); + + if ((targetBuilding.isDoorLocked(doorNumber) == true) && msg.getToggle() == 0x01) { + msg.setUnk1(2); + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; //don't open a locked door + } + + if (msg.getToggle() == 0x01) { + targetBuilding.setDoorState(doorNumber, DoorState.OPEN); + targetBuilding.submitOpenDoorJob(msg.getDoorID()); + } else { + targetBuilding.setDoorState(doorNumber, DoorState.CLOSED); + } + + HashSet container = WorldGrid.getObjectsInRangePartial(targetBuilding, MBServerStatics.CHARACTER_LOAD_RANGE, + MBServerStatics.MASK_PLAYER); + + for (AbstractWorldObject awo : container) { + PlayerCharacter playerCharacter = (PlayerCharacter) awo; + Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + return true; + } + +} diff --git a/src/engine/net/client/handlers/FormationFollowHandler.java b/src/engine/net/client/handlers/FormationFollowHandler.java new file mode 100644 index 00000000..ff78f996 --- /dev/null +++ b/src/engine/net/client/handlers/FormationFollowHandler.java @@ -0,0 +1,75 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.GroupManager; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.FormationFollowMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +public class FormationFollowHandler extends AbstractClientMsgHandler { + + public FormationFollowHandler() { + super(FormationFollowMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) + throws MsgSendException { + FormationFollowMsg msg = (FormationFollowMsg) baseMsg; + + PlayerCharacter source = SessionManager.getPlayerCharacter(origin); + + if (source == null) + return false; + + Group group = GroupManager.getGroup(source); + + if (group == null) + return false; + + if (msg.isFollow()) {// Toggle follow + source.toggleFollow(); + if (group.getGroupLead() == source) { + if (source.getFollow()) { + ChatManager.chatGroupInfo(source, "falls into formation"); + } else { + ChatManager.chatGroupInfo(source, "breaks formation"); + } + } else { + if (source.getFollow()) { + ChatManager.chatGroupInfo(source, source.getFirstName() + " falls into formation"); + } else { + ChatManager.chatGroupInfo(source, source.getFirstName() + " breaks formation"); + } + } + GroupUpdateMsg gum = new GroupUpdateMsg(); + gum.setGroup(group); + gum.setPlayer(source); + gum.setMessageType(8); + + group.sendUpdate(gum); + } else {// Set Formation + if (group.getGroupLead() != source) { + return false; + } + group.setFormation(msg.getFormation()); + + } + return true; + } + +} diff --git a/src/engine/net/client/handlers/FriendAcceptHandler.java b/src/engine/net/client/handlers/FriendAcceptHandler.java new file mode 100644 index 00000000..4212f2ea --- /dev/null +++ b/src/engine/net/client/handlers/FriendAcceptHandler.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.AcceptFriendMsg; +import engine.net.client.msg.AddFriendMessage; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.PlayerCharacter; +import engine.objects.PlayerFriends; + +public class FriendAcceptHandler extends AbstractClientMsgHandler { + + public FriendAcceptHandler() { + super(AcceptFriendMsg.class); + } + + //change to Request + public static void HandleAcceptFriend(PlayerCharacter player, AcceptFriendMsg msg) { + PlayerCharacter sourceFriend = SessionManager.getPlayerCharacterByLowerCaseName(msg.sourceName); + + if (sourceFriend == null) { + ErrorPopupMsg.sendErrorMsg(player, "Could not find player " + msg.sourceName); + return; + } + + PlayerFriends.AddToFriends(sourceFriend.getObjectUUID(), player.getObjectUUID()); + PlayerFriends.AddToFriends(player.getObjectUUID(), sourceFriend.getObjectUUID()); + + + AddFriendMessage outMsg = new AddFriendMessage(player); + + Dispatch dispatch = Dispatch.borrow(sourceFriend, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + outMsg = new AddFriendMessage(sourceFriend); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ChatManager.chatSystemInfo(sourceFriend, player.getFirstName() + " has agreed to be your friend."); + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + + AcceptFriendMsg msg = (AcceptFriendMsg) baseMsg; + + + HandleAcceptFriend(player, msg); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/FriendDeclineHandler.java b/src/engine/net/client/handlers/FriendDeclineHandler.java new file mode 100644 index 00000000..eb57b3f4 --- /dev/null +++ b/src/engine/net/client/handlers/FriendDeclineHandler.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.DeclineFriendMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.PlayerCharacter; + +public class FriendDeclineHandler extends AbstractClientMsgHandler { + + public FriendDeclineHandler() { + super(DeclineFriendMsg.class); + } + + //change to Request + public static void HandleDeclineFriend(PlayerCharacter player, DeclineFriendMsg msg) { + PlayerCharacter sourceFriend = SessionManager.getPlayerCharacterByLowerCaseName(msg.sourceName); + + if (sourceFriend == null) { + ErrorPopupMsg.sendErrorMsg(player, "Could not find player " + msg.sourceName); + return; + } + + Dispatch dispatch = Dispatch.borrow(sourceFriend, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + + DeclineFriendMsg msg = (DeclineFriendMsg) baseMsg; + + + HandleDeclineFriend(player, msg); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/FriendRequestHandler.java b/src/engine/net/client/handlers/FriendRequestHandler.java new file mode 100644 index 00000000..48c5725a --- /dev/null +++ b/src/engine/net/client/handlers/FriendRequestHandler.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.FriendRequestMsg; +import engine.objects.PlayerCharacter; + +public class FriendRequestHandler extends AbstractClientMsgHandler { + + public FriendRequestHandler() { + super(FriendRequestMsg.class); + } + + public static void HandleRequestFriend(PlayerCharacter player, FriendRequestMsg msg) { + PlayerCharacter targetFriend = SessionManager.getPlayerCharacterByLowerCaseName(msg.friendName); + + if (targetFriend == null) { + ErrorPopupMsg.sendErrorMsg(player, "Could not find player " + msg.friendName); + return; + } + + if (targetFriend.equals(player)) + return; + + + Dispatch dispatch = Dispatch.borrow(targetFriend, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + ChatManager.chatSystemInfo(player, "Your friend request has been sent."); + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + + FriendRequestMsg msg = (FriendRequestMsg) baseMsg; + + HandleRequestFriend(player, msg); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/FurnitureHandler.java b/src/engine/net/client/handlers/FurnitureHandler.java new file mode 100644 index 00000000..e7dc902d --- /dev/null +++ b/src/engine/net/client/handlers/FurnitureHandler.java @@ -0,0 +1,48 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.FurnitureMsg; +import engine.objects.PlayerCharacter; + +public class FurnitureHandler extends AbstractClientMsgHandler { + + public FurnitureHandler() { + super(FurnitureMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + FurnitureMsg msg = (FurnitureMsg) baseMsg; + + PlayerCharacter pc = origin.getPlayerCharacter(); + if (pc == null) { + return false; + } + + if (msg.getType() == 1) + msg.setType(2); + + if (msg.getType() == 3) + msg.setType(2); + Dispatch dispatch = Dispatch.borrow(pc, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + +} diff --git a/src/engine/net/client/handlers/GroupInviteHandler.java b/src/engine/net/client/handlers/GroupInviteHandler.java new file mode 100644 index 00000000..bef9a198 --- /dev/null +++ b/src/engine/net/client/handlers/GroupInviteHandler.java @@ -0,0 +1,126 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.GroupManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.GroupInviteMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +public class GroupInviteHandler extends AbstractClientMsgHandler { + + public GroupInviteHandler() { + super(GroupInviteMsg.class); + } + + private static Group createGroup(PlayerCharacter pc, ClientConnection origin) { + + if (pc == null) + return null; + + Group group = new Group(pc, GroupManager.incrGroupCount()); + group.addGroupMember(pc); + GroupManager.addNewGroup(group); + + pc.setFollow(false); + // Send add self to group message + GroupUpdateMsg msg = new GroupUpdateMsg(); + msg.setGroup(group); + msg.setPlayer(pc); + msg.setMessageType(1); + Dispatch dispatch = Dispatch.borrow(pc, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + group.addUpdateGroupJob(); + + return group; + } + + // this can only be called if you already know you are not in a group + // and have issued an invite + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + + ClientConnection origin) throws MsgSendException { + GroupInviteMsg msg = (GroupInviteMsg) baseMsg; + PlayerCharacter source = SessionManager.getPlayerCharacter(origin); + + if (source == null) + return false; + + Group group = GroupManager.getGroup(source); + + // Group is new, create it. + + if (group == null) + group = GroupInviteHandler.createGroup(source, origin); + + if (group == null) + return false; + + if (!group.isGroupLead(source)) // person doing invite must be group lead + return true; + + PlayerCharacter target = null; + + if (msg.getInvited() == 1) { // Use name for invite + target = SessionManager.getPlayerCharacterByLowerCaseName(msg.getName().toLowerCase()); + } else { // Use ID for invite + target = SessionManager.getPlayerCharacterByID(msg.getTargetID()); + } + + if (target == null) + return false; + + // Client must be online + + if (SessionManager.getClientConnection(target) == null) + return false; + + if (source == target) // Inviting self, so we're done + return false; + + + //Skip invite if target is ignoring source + + if (target.isIgnoringPlayer(source)) + return false; + + + // dont block invites to people already in a group and + // dont check for pending invites, the client does it + // Send invite message to target + + msg.setSourceType(GameObjectType.PlayerCharacter.ordinal()); + msg.setSourceID(source.getObjectUUID()); + msg.setTargetType(0); + msg.setTargetID(0); + msg.setGroupType(GameObjectType.Group.ordinal()); + msg.setGroupID(group.getObjectUUID()); + msg.setInvited(1); + msg.setName(source.getFirstName()); + + Dispatch dispatch = Dispatch.borrow(target, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/GroupInviteResponseHandler.java b/src/engine/net/client/handlers/GroupInviteResponseHandler.java new file mode 100644 index 00000000..a0706f06 --- /dev/null +++ b/src/engine/net/client/handlers/GroupInviteResponseHandler.java @@ -0,0 +1,112 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.GroupManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.GroupInviteResponseMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +import java.util.Set; + +import static engine.util.KeyCloneAudit.KEYCLONEAUDIT; + +public class GroupInviteResponseHandler extends AbstractClientMsgHandler { + + public GroupInviteResponseHandler() { + super(GroupInviteResponseMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + GroupInviteResponseMsg msg = (GroupInviteResponseMsg) baseMsg; + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return false; + + // if we are already in a group we are leaving it + + Group currGroup = GroupManager.getGroup(player); + + if (currGroup != null) // if we are already in a group we are leaving it + GroupManager.LeaveGroup(player); + + // not sure we need to test for invites to wrong grp as only + // 1 invite can be on screen at a time + //if (invitesPending.get(player) != msg.getGroupID()) // Can't accept + // invite to + // wrong group + // return; + + Group group = GroupManager.getGroup(msg.getGroupID()); + + if (group == null) + return false; + + if (group.addGroupMember(player) == false) + return false; + { + player.setFollow(true); + GroupManager.addPlayerGroupMapping(player, group); + Set members = group.getMembers(); + GroupUpdateMsg groupUpdateMsg; + + // Send all group members to player added + for (PlayerCharacter groupMember : members) { + + groupUpdateMsg = new GroupUpdateMsg(); + groupUpdateMsg.setGroup(group); + groupUpdateMsg.setMessageType(1); + + if (groupMember == null) + continue; + + if (groupMember.equals(player)) + continue; + + groupUpdateMsg.setPlayer(groupMember); + + Dispatch dispatch = Dispatch.borrow(player, groupUpdateMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + // send new group member to everyone in group. + groupUpdateMsg = new GroupUpdateMsg(); + groupUpdateMsg.setGroup(group); + groupUpdateMsg.setMessageType(1); + groupUpdateMsg.setPlayer(player); + group.sendUpdate(groupUpdateMsg); + + String text = player.getFirstName() + " has joined the group."; + ChatManager.chatGroupInfo(player, text); + + // Run Keyclone Audit + + KEYCLONEAUDIT.audit(player, group); + + return true; + } + } +} + + diff --git a/src/engine/net/client/handlers/GroupUpdateHandler.java b/src/engine/net/client/handlers/GroupUpdateHandler.java new file mode 100644 index 00000000..ad2a2d55 --- /dev/null +++ b/src/engine/net/client/handlers/GroupUpdateHandler.java @@ -0,0 +1,33 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.GroupUpdateMsg; + +public class GroupUpdateHandler extends AbstractClientMsgHandler { + + public GroupUpdateHandler() { + super(GroupUpdateMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + //GroupUpdateMsg msg = (GroupUpdateMsg) baseMsg; + // not sure what to do with these as we spend our time sending them + // to the whole group ourselves + return true; + } + +} diff --git a/src/engine/net/client/handlers/GuildControlHandler.java b/src/engine/net/client/handlers/GuildControlHandler.java new file mode 100644 index 00000000..1654dbae --- /dev/null +++ b/src/engine/net/client/handlers/GuildControlHandler.java @@ -0,0 +1,74 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.GuildControlMsg; +import engine.net.client.msg.guild.GuildListMsg; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class GuildControlHandler extends AbstractClientMsgHandler { + + public GuildControlHandler() { + super(GuildControlMsg.class); + } + + // TODO Don't think this protocolMsg (0x3235E5EA) is actually player history. so + // take further look at it. + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + GuildControlMsg msg = (GuildControlMsg) baseMsg; + Dispatch dispatch; + + // until we know what it's for, just echo it back. + msg.setUnknown05((byte) 1); + + //Send a GuildList msg + if (msg.getUnknown01() == 1) { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + //TODO figure out why GL can't be changed and IC can't be banished + //Bounce back the rank options + msg.setGM((byte) (GuildStatusController.isGuildLeader(player.getGuildStatus()) ? 1 : 0)); + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + if (GuildStatusController.isInnerCouncil(player.getGuildStatus()) || GuildStatusController.isGuildLeader(player.getGuildStatus())) { + dispatch = Dispatch.borrow(player, new GuildListMsg(player.getGuild())); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } else + ErrorPopupMsg.sendErrorMsg(player, "Only guild leader and inner council have such authority!"); + + + } else if (msg.getUnknown01() == 2) { + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + + //If we don't get a valid PC for whatever reason.. just ignore it. + PlayerCharacter pc = PlayerCharacter.getFromCache(msg.getUnknown03()); + + if (pc != null) { + dispatch = Dispatch.borrow(player, new GuildListMsg(pc)); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + } + + return true; + } + +} diff --git a/src/engine/net/client/handlers/GuildCreationCloseHandler.java b/src/engine/net/client/handlers/GuildCreationCloseHandler.java new file mode 100644 index 00000000..3b99b92a --- /dev/null +++ b/src/engine/net/client/handlers/GuildCreationCloseHandler.java @@ -0,0 +1,30 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.GuildCreationCloseMsg; + +public class GuildCreationCloseHandler extends AbstractClientMsgHandler { + + public GuildCreationCloseHandler() { + super(GuildCreationCloseMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { +// GuildCreationCloseMsg msg = (GuildCreationCloseMsg) baseMsg; +// origin.sendMsg(msg); + return true; + } + +} diff --git a/src/engine/net/client/handlers/GuildCreationFinalizeHandler.java b/src/engine/net/client/handlers/GuildCreationFinalizeHandler.java new file mode 100644 index 00000000..aed341f4 --- /dev/null +++ b/src/engine/net/client/handlers/GuildCreationFinalizeHandler.java @@ -0,0 +1,147 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.Enum.GuildHistoryType; +import engine.Enum.ItemType; +import engine.Enum.OwnerType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.GuildCreationFinalizeMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.objects.Guild; +import engine.objects.Item; +import engine.objects.ItemBase; +import engine.objects.PlayerCharacter; +import engine.util.StringUtils; + +public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler { + + public GuildCreationFinalizeHandler() { + super(GuildCreationFinalizeMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + GuildCreationFinalizeMsg msg; + Enum.GuildType charterType; + Guild newGuild; + Guild playerGuild; + ItemBase itemBase; + Item charter; + Dispatch dispatch; + + msg = (GuildCreationFinalizeMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + playerGuild = player.getGuild(); + + if (playerGuild.isEmptyGuild() == false && player.getGuild().getGuildLeaderUUID() == player.getObjectUUID()) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.MUST_LEAVE_GUILD); + return true; + } + + //Validate the Charter + + charter = msg.getCharter(); + + if (charter == null || charter.getOwnerType() != OwnerType.PlayerCharacter || charter.getOwnerID() != player.getObjectUUID()) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND); + return true; + } + + itemBase = charter.getItemBase(); + + // Item must be a valid charterType (type 10 in db) + + if (itemBase == null || (itemBase.getType().equals(ItemType.GUILDCHARTER) == false)) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND); + return true; + } + + charterType = Enum.GuildType.getGuildTypeFromCharter(itemBase); + + if (charterType == null) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND); + return true; + } + + //Validate Guild Tags + + if (!msg.getGuildTag().isValid()) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.CREST_RESERVED); + return true; + } + + // Validation passes. Leave current guild and create new one. + + if (player.getGuild() != null && player.getGuild().isEmptyGuild() == false) + player.getGuild().removePlayer(player, GuildHistoryType.LEAVE); + + int leadershipType = ((msg.getICVoteFlag() << 1) | msg.getMemberVoteFlag()); + + newGuild = new Guild(msg.getName(), null, charterType.ordinal(), + charterType.getLeadershipType(leadershipType), msg.getGuildTag(), + StringUtils.truncate(msg.getMotto(), 120)); + + newGuild.setGuildLeaderForCreate(player); + + synchronized (this) { + if (!DbManager.GuildQueries.IS_NAME_UNIQUE(msg.getName())) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.UNIQUE_NAME); + return true; + } + + if (!DbManager.GuildQueries.IS_CREST_UNIQUE(msg.getGuildTag())) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.UNIQUE_CREST); + return true; + } + + newGuild = DbManager.GuildQueries.SAVE_TO_DATABASE(newGuild); + } + + if (newGuild == null) { + ErrorPopupMsg.sendErrorPopup(player, GuildManager.FAILURE_TO_SWEAR_GUILD); + return true; + } + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + GuildManager.joinGuild(player, newGuild, GuildHistoryType.CREATE); + + newGuild.setGuildLeader(player); + player.setGuildLeader(true); + player.setInnerCouncil(true); + player.setFullMember(true); + player.setGuildTitle(charterType.getNumberOfRanks() - 1); + player.getCharItemManager().delete(charter); + player.getCharItemManager().updateInventory(); + player.incVer(); + + DispatchMessage.sendToAllInRange(player, new GuildInfoMsg(player, newGuild, 2)); + + ChatManager.chatSystemInfo(player, msg.getName() + " has arrived on Grief server!"); + + return true; + } +} diff --git a/src/engine/net/client/handlers/GuildCreationOptionsHandler.java b/src/engine/net/client/handlers/GuildCreationOptionsHandler.java new file mode 100644 index 00000000..8b957be1 --- /dev/null +++ b/src/engine/net/client/handlers/GuildCreationOptionsHandler.java @@ -0,0 +1,47 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.GuildCreationOptionsMsg; +import engine.objects.PlayerCharacter; + +public class GuildCreationOptionsHandler extends AbstractClientMsgHandler { + + public GuildCreationOptionsHandler() { + super(GuildCreationOptionsMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + GuildCreationOptionsMsg msg = (GuildCreationOptionsMsg) baseMsg; + PlayerCharacter sourcePlayer = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (msg.getScreenType() == 1) { + msg.setScreenType(3); + } else if (msg.getScreenType() == 2) { + msg.setScreenType(4); + } + + if (sourcePlayer == null) + return true; + + dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/GuildInfoHandler.java b/src/engine/net/client/handlers/GuildInfoHandler.java new file mode 100644 index 00000000..550cdd0a --- /dev/null +++ b/src/engine/net/client/handlers/GuildInfoHandler.java @@ -0,0 +1,67 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.objects.PlayerCharacter; + +public class GuildInfoHandler extends AbstractClientMsgHandler { + + public GuildInfoHandler() { + super(GuildInfoMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + GuildInfoMsg msg = (GuildInfoMsg) baseMsg; + Dispatch dispatch; + + // get source player + PlayerCharacter sourcePlayer = SessionManager + .getPlayerCharacter(origin); + + if (sourcePlayer == null) + return true; + + if (msg.getMsgType() == 1) { + dispatch = Dispatch.borrow(sourcePlayer, new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 4)); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } else if (msg.getMsgType() == 5) { + + if (msg.getObjectType() == GameObjectType.PlayerCharacter.ordinal()) { + PlayerCharacter pc = PlayerCharacter.getPlayerCharacter(msg.getObjectID()); + dispatch = Dispatch.borrow(sourcePlayer, new GuildInfoMsg(pc, pc.getGuild(), 5)); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } else { + //TODO Change this to a null object when we make a null object. + + dispatch = Dispatch.borrow(sourcePlayer, new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 1)); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } + } + + // Send PromoteDemoteScreen info message response. 0x001D4DF6 + + // Send guild member list? 0x6949C720 + // GuildList(source, origin); + + // send 0x3235E5EA? See what that is + + return true; + } + +} diff --git a/src/engine/net/client/handlers/GuildListHandler.java b/src/engine/net/client/handlers/GuildListHandler.java new file mode 100644 index 00000000..c3aea390 --- /dev/null +++ b/src/engine/net/client/handlers/GuildListHandler.java @@ -0,0 +1,36 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.GuildListMsg; + +public class GuildListHandler extends AbstractClientMsgHandler { + + public GuildListHandler() { + super(GuildListMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { +// GuildListMsg msg = (GuildListMsg) baseMsg; + + // GuildListMsg msg = new GuildListMsg(origin); + // GuildTableList gtl = new GuildTableList(); + // gtl.setUUID(pc.getUUID()); gtl.setName(pc.getName()); + // msg.add(gtl); + // origin.sendMsg(msg); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/GuildUnknownHandler.java b/src/engine/net/client/handlers/GuildUnknownHandler.java new file mode 100644 index 00000000..9657fa1d --- /dev/null +++ b/src/engine/net/client/handlers/GuildUnknownHandler.java @@ -0,0 +1,29 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.GuildUnknownMsg; + +public class GuildUnknownHandler extends AbstractClientMsgHandler { + + public GuildUnknownHandler() { + super(GuildUnknownMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { +// GuildUnknownMsg msg = (GuildUnknownMsg) baseMsg; + return true; + } + +} diff --git a/src/engine/net/client/handlers/HirelingServiceMsgHandler.java b/src/engine/net/client/handlers/HirelingServiceMsgHandler.java new file mode 100644 index 00000000..68ee899c --- /dev/null +++ b/src/engine/net/client/handlers/HirelingServiceMsgHandler.java @@ -0,0 +1,81 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.HirelingServiceMsg; +import engine.net.client.msg.ManageNPCMsg; +import engine.objects.Building; +import engine.objects.NPC; +import engine.objects.PlayerCharacter; + +public class HirelingServiceMsgHandler extends AbstractClientMsgHandler { + + public HirelingServiceMsgHandler() { + super(HirelingServiceMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + HirelingServiceMsg msg; + + msg = (HirelingServiceMsg) baseMsg; + + // get PlayerCharacter of person accepting invite + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + switch (msg.messageType) { + case HirelingServiceMsg.SETREPAIRCOST: + Building building = BuildingManager.getBuildingFromCache(msg.buildingID); + + if (building == null) + return true; + + NPC npc = NPC.getFromCache(msg.npcID); + + if (npc == null) + return true; + + if (!BuildingManager.playerCanManage(player, building)) + return true; + + + npc.setRepairCost(msg.repairCost); + ManageNPCMsg outMsg = new ManageNPCMsg(npc); + Dispatch dispatch = Dispatch.borrow(player, msg); + + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + dispatch = Dispatch.borrow(player, outMsg); + + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + break; + } + + + return true; + + + } + +} diff --git a/src/engine/net/client/handlers/InviteToGuildHandler.java b/src/engine/net/client/handlers/InviteToGuildHandler.java new file mode 100644 index 00000000..cce00445 --- /dev/null +++ b/src/engine/net/client/handlers/InviteToGuildHandler.java @@ -0,0 +1,149 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.GuildManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.InviteToGuildMsg; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class InviteToGuildHandler extends AbstractClientMsgHandler { + + public InviteToGuildHandler() { + super(InviteToGuildMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + InviteToGuildMsg msg; + PlayerCharacter sourcePlayer; + PlayerCharacter targetPlayer; + Dispatch dispatch; + + msg = (InviteToGuildMsg) baseMsg; + + // First see if this is a refusal to another guild invite + + if (msg.getResponse() == 4) + return true; // Player refused invite + + // get sourcePlayer player + + sourcePlayer = SessionManager.getPlayerCharacter(origin); + + if (sourcePlayer == null) + return true; + + if (msg.getTargetUUID() == 0) { + // get targetPlayer player by name + targetPlayer = SessionManager.getPlayerCharacterByLowerCaseName(msg.getTargetName()); + + if (targetPlayer == null) { + ChatManager.chatGuildError(sourcePlayer, + "No such player exists!"); + return true; + } + } else if (msg.getTargetType() == GameObjectType.PlayerCharacter.ordinal()) { + + targetPlayer = SessionManager.getPlayerCharacterByID(msg.getTargetUUID()); + + if (targetPlayer == null) { + ChatManager.chatGuildError(sourcePlayer, + "No such player exists!"); + return true; + } + } else { + ChatManager.chatGuildError(sourcePlayer, + "You cannot invite that character!"); + return true; + } + + // get sourcePlayer guild. Verify sourcePlayer player is in guild + + if (sourcePlayer.getGuild().getObjectUUID() == 0 || sourcePlayer.getGuild().isEmptyGuild()) { + ChatManager.chatGuildError(sourcePlayer, + "You cannot invite someone for errant!"); + return true; + } + + Enum.GuildType guildType = Enum.GuildType.values()[sourcePlayer.getGuild().getCharter()]; + + if (guildType == null) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, GuildManager.NO_CHARTER_FOUND); + return true; + } + + + // verify sourcePlayer player is full member so they can invite + + if (GuildStatusController.isFullMember(sourcePlayer.getGuildStatus()) == false) { + ChatManager.chatGuildError(sourcePlayer, + "You do not have authority to invite!"); + return true; + } + + //block invite is targetPlayer is ignoring sourcePlayer + + if (targetPlayer.isIgnoringPlayer(sourcePlayer)) + return true; + + if ((targetPlayer.getGuild().isEmptyGuild() == false)) { + ChatManager.chatGuildError(sourcePlayer, + targetPlayer.getFirstName() + " already belongs to a guild!"); + return true; + } + + // verify targetPlayer player is not on banish list + + if (sourcePlayer.getGuild().getBanishList().contains(targetPlayer)) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 135);// Character is considered BANISHED by guild leadership + return true; + } + + //verify targetPlayer meets level requirements of guild + + if ((targetPlayer.getLevel() < sourcePlayer.getGuild().getRepledgeMin()) || targetPlayer.getLevel() > sourcePlayer.getGuild().getRepledgeMax()) { + ErrorPopupMsg.sendErrorPopup(sourcePlayer, 135);// you do not meet the level required for this SWORN guild + return true; + } + + targetPlayer.setLastGuildToInvite(sourcePlayer.getGuild().getObjectUUID()); + + // setup guild invite message to send to targetPlayer + + msg.setSourceType(sourcePlayer.getObjectType().ordinal()); + msg.setSourceUUID(sourcePlayer.getObjectUUID()); + msg.setTargetType(targetPlayer.getObjectType().ordinal()); + + msg.setTargetUUID(targetPlayer.getObjectUUID()); + msg.setGuildTag(sourcePlayer.getGuild().getGuildTag()); + msg.setGuildName(sourcePlayer.getGuild().getName()); + msg.setGuildType(sourcePlayer.getGuild().getObjectType().ordinal()); + msg.setGuildUUID(sourcePlayer.getGuild().getObjectUUID()); + msg.setTargetName(""); + + dispatch = Dispatch.borrow(targetPlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/InviteToSubHandler.java b/src/engine/net/client/handlers/InviteToSubHandler.java new file mode 100644 index 00000000..bab06111 --- /dev/null +++ b/src/engine/net/client/handlers/InviteToSubHandler.java @@ -0,0 +1,133 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.InviteToSubMsg; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class InviteToSubHandler extends AbstractClientMsgHandler { + + public InviteToSubHandler() { + super(InviteToSubMsg.class); + } + + private static void sendChat(PlayerCharacter source, String msg) { + ChatManager.chatGuildError(source, msg); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter source; + PlayerCharacter target; + Guild sourceGuild; + Guild targetGuild; + InviteToSubMsg msg = (InviteToSubMsg) baseMsg; + Dispatch dispatch; + + source = SessionManager.getPlayerCharacter(origin); + + if (source == null) + return true; + + target = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getTargetUUID()); + + if (target == null) { + ErrorPopupMsg.sendErrorMsg(source, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return true; + } + + //Ignore invites to sub if ignoring player + + if (target.isIgnoringPlayer(source)) + return true; + + sourceGuild = source.getGuild(); + targetGuild = target.getGuild(); + + //source must be in guild + + if (sourceGuild == null) { + sendChat(source, "You must be in a guild to invite to sub."); + return true; + } + + if (sourceGuild.isEmptyGuild()) { + sendChat(source, "You must be in a guild to invite to sub."); + return true; + } + + //source must be GL or IC + + if (GuildStatusController.isInnerCouncil(source.getGuildStatus()) == false) { + sendChat(source, "Only guild leadership can invite to sub."); + return true; + } + + if (sourceGuild.getNation().isEmptyGuild()) + return true; + + //target must be in a guild + + if (targetGuild == null) + return true; + + if (sourceGuild.equals(targetGuild)) + return true; + + //target must be GL or IC + + if (GuildStatusController.isInnerCouncil(target.getGuildStatus()) == false && GuildStatusController.isGuildLeader(target.getGuildStatus()) == false) { + sendChat(source, "Target player is not guild leadership."); + return true; + } + + //Can't already be same nation or errant + //source guild is limited to 7 subs + //TODO this should be based on TOL rank + + + if (!sourceGuild.canSubAGuild(targetGuild)) { + sendChat(source, "This Guild can't be subbed."); + return true; + } + + //all tests passed, let's send invite. + + if (target.getClientConnection() != null) { + msg.setGuildTag(sourceGuild.getGuildTag()); + msg.setGuildName(sourceGuild.getName()); + msg.setGuildUUID(sourceGuild.getObjectUUID()); + msg.setUnknown02(1); + + dispatch = Dispatch.borrow(target, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } else { + sendChat(source, "Failed to send sub invite to target."); + return true; + } + + return true; + } +} diff --git a/src/engine/net/client/handlers/ItemProductionMsgHandler.java b/src/engine/net/client/handlers/ItemProductionMsgHandler.java new file mode 100644 index 00000000..3134f939 --- /dev/null +++ b/src/engine/net/client/handlers/ItemProductionMsgHandler.java @@ -0,0 +1,511 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.ItemType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.ItemProductionMsg; +import engine.net.client.msg.ManageNPCMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.HashMap; + +/* + * @Summary: Processes application protocol message which modifies + * hireling inventory through rolling, junking or depositing. + */ +public class ItemProductionMsgHandler extends AbstractClientMsgHandler { + + private static final int ACTION_PRODUCE = 1; + private static final int ACTION_JUNK = 2; + private static final int ACTION_RECYCLE = 3; + private static final int ACTION_COMPLETE = 4; + private static final int ACTION_DEPOSIT = 6; + private static final int ACTION_SETPRICE = 5; + private static final int ACTION_TAKE = 7; + private static final int ACTION_CONFIRM_SETPRICE = 9; // Unsure. Sent by client + private static final int ACTION_CONFIRM_DEPOSIT = 10; // Unsure. Sent by client + private static final int ACTION_CONFIRM_TAKE = 11; // Unsure. Sent by client + + public ItemProductionMsgHandler() { + super(ItemProductionMsg.class); + } + + private static void setItemPrice(int itemType, int itemUUID, int itemPrice, NPC vendor, ClientConnection origin) { + + Item targetItem; + ItemProductionMsg outMsg; + Dispatch dispatch; + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return; + + if (itemType == GameObjectType.Item.ordinal()) + targetItem = Item.getFromCache(itemUUID); + else if (itemType == GameObjectType.MobLoot.ordinal()) + targetItem = MobLoot.getFromCache(itemUUID); + else + targetItem = null; + + + if (targetItem == null) + return; + + if (targetItem.getObjectType() == GameObjectType.Item) { + if (!DbManager.ItemQueries.UPDATE_VALUE(targetItem, itemPrice)) { + ChatManager.chatInfoError(origin.getPlayerCharacter(), "Failed to set price! Contact CCR For help."); + return; + } + targetItem.setValue(itemPrice); + outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_DEPOSIT, true); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_SETPRICE, true); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } else if (targetItem.getObjectType() == GameObjectType.MobLoot) { + MobLoot mobLoot = (MobLoot) targetItem; + if (!DbManager.NPCQueries.UPDATE_ITEM_PRICE(mobLoot.getObjectUUID(), vendor.getObjectUUID(), itemPrice)) { + ChatManager.chatInfoError(origin.getPlayerCharacter(), "Failed to set price! Contact CCR For help."); + return; + } + targetItem.setValue(itemPrice); + outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_DEPOSIT, true); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_SETPRICE, true); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + // Set item's price + + + } + + // Method sets the price on an item in the vendor inventory + + private static void depositItem(int itemUUID, NPC vendor, ClientConnection origin) { + + Item targetItem; + ItemProductionMsg outMsg; + CharacterItemManager itemMan; + Dispatch dispatch; + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return; + + if (origin.sellLock.tryLock()) { + try { + targetItem = Item.getFromCache(itemUUID); + + if (targetItem == null) + return; + + if (targetItem.getItemBase().getType() == ItemType.GOLD) + return; + + if (!vendor.getCharItemManager().hasRoomInventory(targetItem.getItemBase().getWeight())) { + + ErrorPopupMsg.sendErrorPopup(player, 21); + return; + } + + itemMan = origin.getPlayerCharacter().getCharItemManager(); + + if (itemMan == null) + return; + + if (vendor.getCharItemManager().getInventoryWeight() > 500) { + ErrorPopupMsg.sendErrorPopup(player, 21); + return; + } + + if (!targetItem.validForInventory(origin, player, itemMan)) { + ErrorPopupMsg.sendErrorPopup(player, 19); + return; + } + + // Transfer item from player to vendor's inventory + + if (!itemMan.sellToNPC(targetItem, vendor)) { + ErrorPopupMsg.sendErrorPopup(player, 109); + return; + } + + + outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_DEPOSIT, true); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_CONFIRM_DEPOSIT, true); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + origin.getPlayerCharacter().getCharItemManager().updateInventory(); + } catch (Exception e) { + Logger.error(e); + } finally { + origin.sellLock.unlock(); + } + + } + + + } + + // Method adds an item from the players inventory to the vendor. + + private static void completeItem(int itemUUID, NPC vendor, ClientConnection origin, ItemProductionMsg msg) { + + Item targetItem; + ManageNPCMsg outMsg; + Dispatch dispatch; + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return; + + if (origin.buyLock.tryLock()) { + try { + targetItem = Item.getFromCache(itemUUID); + + if (targetItem == null) + return; + + + if (!vendor.getCharItemManager().forgeContains(targetItem, vendor)) + return; + + boolean worked = DbManager.ItemQueries.UPDATE_FORGE_TO_INVENTORY(targetItem); + if (!worked) { + Guild guild = vendor.getGuild(); + if (guild == null) + return; + //ChatManager.chatGuildInfo(guild, "Failed to complete Item " + targetItem.getName()); + return; + } + + targetItem.containerType = Enum.ItemContainerType.INVENTORY; + targetItem.setOwner(vendor); + vendor.getCharItemManager().addItemToInventory(targetItem); + + vendor.removeItemFromForge(targetItem); + + outMsg = new ManageNPCMsg(vendor); + outMsg.setMessageType(ACTION_PRODUCE); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } finally { + origin.buyLock.unlock(); + } + } + } + + // Method completes an item that has been previously rolled + // adding it to the NPC's inventory + + private static void recycleItem(HashMap itemList, NPC vendor, ClientConnection origin) { + + Item targetItem; + ItemProductionMsg outMsg; + int totalValue = 0; + int currentStrongbox; + Dispatch dispatch; + + if (vendor.getBuilding() == null) + return; + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return; + + if (itemList == null) + return; + + if (origin.sellLock.tryLock()) { + try { + + + for (int itemUUID : itemList.keySet()) { + int itemValue = 0; + + int type = itemList.get(itemUUID); + + if (type == GameObjectType.Item.ordinal()) + targetItem = Item.getFromCache(itemUUID); + else + targetItem = MobLoot.getFromCache(itemUUID); + + if (targetItem == null) + continue; + + if (targetItem.getItemBase().getType() == ItemType.GOLD) + return; + + if (!vendor.getCharItemManager().doesCharOwnThisItem(targetItem.getObjectUUID())) + continue; + if (vendor.getCharItemManager().inventoryContains(targetItem) == false) + continue; + + itemValue = targetItem.getBaseValue(); + + if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(player, 201); + break; + } + + switch (targetItem.getItemBase().getType()) { + case CONTRACT: + case GUILDCHARTER: + case DEED: + case REALMCHARTER: + case SCROLL: + case TEARS: + itemValue = 0; + continue; + } + totalValue += itemValue; + long start = System.currentTimeMillis(); + vendor.getCharItemManager().recycle(targetItem); + long end = System.currentTimeMillis(); + long timetook = end - start; + + // ChatManager.chatSystemInfo(player, "Took " + timetook + " ms to finish"); + + outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_TAKE, true); + + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + // Refund a portion of the gold + + if (!vendor.getBuilding().transferGold(totalValue, false)) + return; + + + } catch (Exception e) { + Logger.error(e); + } finally { + + origin.sellLock.unlock(); + } + + } + + // Refresh vendor's inventory to client + + } + + // Method handles recycling of an item + + private static void junkItem(int itemUUID, NPC vendor, ClientConnection origin) { + + MobLoot targetItem; + ManageNPCMsg outMsg; + Dispatch dispatch; + + if (origin.sellLock.tryLock()) { + try { + targetItem = MobLoot.getFromCache(itemUUID); + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return; + + // Can't junk nothing! + + if (targetItem == null) + return; + + + if (!vendor.getCharItemManager().forgeContains(targetItem, vendor)) + return; + + // Cannot junk items without a forge! + + if (vendor.getBuilding() == null) + return; + + // Delete the item and cancel any pending rolling timer jobs + + targetItem.recycle(vendor); + vendor.removeItemFromForge(targetItem); + + // Refresh vendor's inventory to client + + outMsg = new ManageNPCMsg(vendor); + outMsg.setMessageType(1); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + ; + } finally { + origin.sellLock.unlock(); + } + } + + } + + // Method junks an item that has been rolled but not completed + + private static void takeItem(HashMap itemList, NPC vendor, ClientConnection origin) { + + Item targetItem; + + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return; + + + for (int itemUUID : itemList.keySet()) { + + int type = itemList.get(itemUUID); + if (type == GameObjectType.Item.ordinal()) { + targetItem = Item.getFromCache(itemUUID); + + } else { + targetItem = MobLoot.getFromCache(itemUUID); + + } + + if (targetItem == null) + return; + + + if (targetItem.getItemBase().getType() == ItemType.GOLD) + return; + if (vendor.getCharItemManager().inventoryContains(targetItem) == false) + return; + + if (player.getCharItemManager().hasRoomInventory(targetItem.getItemBase().getWeight()) == false) + return; + + player.getCharItemManager().buyFromNPC(targetItem, vendor); + + } + + player.getCharItemManager().updateInventory(); + + // Update NPC inventory to client + + + } + + // Method removes item from an NPC's inventory and transferes it to a player + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + NPC vendorNPC; + ItemProductionMsg msg; + Dispatch dispatch; + + // Member variable assignment + + msg = (ItemProductionMsg) baseMsg; + player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + // Grab reference to vendor we are interacting with + + vendorNPC = (NPC) DbManager.getObject(engine.Enum.GameObjectType.NPC, msg.getNpcUUID()); + + // Oops? + + if (vendorNPC == null) + return true; + + // Process Request + + switch (msg.getActionType()) { + + case ACTION_PRODUCE: + boolean isRandom = false; + if (msg.getUnknown03() != 0 && msg.getpToken() == 0 && msg.getsToken() == 0) + isRandom = true; + //Create Multiple Item Function.. Fill all empty slots + if (msg.isMultiple()) { + int emptySlots = vendorNPC.getRank() - vendorNPC.getRolling().size(); + if (emptySlots > 0) { + for (int i = 0; i < emptySlots; i++) { + vendorNPC.produceItem(player.getObjectUUID(), msg.getTotalProduction(), isRandom, msg.getpToken(), msg.getsToken(), msg.getName(), msg.getItemUUID()); + } + } + } else + vendorNPC.produceItem(player.getObjectUUID(), msg.getTotalProduction(), isRandom, msg.getpToken(), msg.getsToken(), msg.getName(), msg.getItemUUID()); + break; + case ACTION_JUNK: + junkItem(msg.getItemUUID(), vendorNPC, origin); + break; + case ACTION_RECYCLE: + recycleItem(msg.getItemIDtoTypeMap(), vendorNPC, origin); + msg.setActionType(7); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + case ACTION_COMPLETE: + + vendorNPC.completeItem(msg.getItemUUID()); + + // ManageNPCMsg outMsg = new ManageNPCMsg(vendorNPC); + // outMsg.setMessageType(1); + // + // dispatch = Dispatch.borrow(player, outMsg); + // DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + break; + case ACTION_DEPOSIT: + depositItem(msg.getItemUUID(), vendorNPC, origin); + break; + case ACTION_SETPRICE: + setItemPrice(msg.getItemType(), msg.getItemUUID(), msg.getItemPrice(), vendorNPC, origin); + break; + case ACTION_TAKE: + takeItem(msg.getItemIDtoTypeMap(), vendorNPC, origin); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + + } + return true; + } + + // Method handles rolling item requests from the client + +} diff --git a/src/engine/net/client/handlers/KeepAliveServerClientHandler.java b/src/engine/net/client/handlers/KeepAliveServerClientHandler.java new file mode 100644 index 00000000..da80f487 --- /dev/null +++ b/src/engine/net/client/handlers/KeepAliveServerClientHandler.java @@ -0,0 +1,47 @@ +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.KeepAliveServerClientMsg; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ + +public class KeepAliveServerClientHandler extends AbstractClientMsgHandler { + + public KeepAliveServerClientHandler() { + super(KeepAliveServerClientMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter pc = origin.getPlayerCharacter(); + + + // Member variable declaration + + KeepAliveServerClientMsg msg; + + // Member variable assignment + + msg = (KeepAliveServerClientMsg) baseMsg; + + + // Send ping to client + + Dispatch dispatch = Dispatch.borrow(pc, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/LeaveGroupHandler.java b/src/engine/net/client/handlers/LeaveGroupHandler.java new file mode 100644 index 00000000..52fb7615 --- /dev/null +++ b/src/engine/net/client/handlers/LeaveGroupHandler.java @@ -0,0 +1,31 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.GroupManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.LeaveGroupMsg; + +public class LeaveGroupHandler extends AbstractClientMsgHandler { + + public LeaveGroupHandler() { + super(LeaveGroupMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + GroupManager.LeaveGroup(origin); + return true; + } + +} diff --git a/src/engine/net/client/handlers/LeaveGuildHandler.java b/src/engine/net/client/handlers/LeaveGuildHandler.java new file mode 100644 index 00000000..0483469e --- /dev/null +++ b/src/engine/net/client/handlers/LeaveGuildHandler.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.GuildHistoryType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.LeaveGuildMsg; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.Mine; +import engine.objects.PlayerCharacter; + +public class LeaveGuildHandler extends AbstractClientMsgHandler { + + public LeaveGuildHandler() { + super(LeaveGuildMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + LeaveGuildMsg msg = (LeaveGuildMsg) baseMsg; + Dispatch dispatch; + + // get PlayerCharacter of person leaving invite + + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin); + + if (playerCharacter == null) + return true; + + // Guild leader can't leave guild. must pass GL or disband + + if (GuildStatusController.isGuildLeader(playerCharacter.getGuildStatus())) { + msg.setMessage("You must switch leadership of your guild before leaving!"); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + return true; + } + + // Release all mine claims + + Mine.releaseMineClaims(playerCharacter); + + // get old Guild + Guild oldGuild = playerCharacter.getGuild(); + + if (oldGuild == null || oldGuild.isEmptyGuild()) { + return true; + } + + // Send left guild message to rest of guild + ChatManager.chatGuildInfo(oldGuild, playerCharacter.getFirstName() + " has left the guild."); + + oldGuild.removePlayer(playerCharacter, GuildHistoryType.LEAVE); + + // Send message back to client + msg.setMessage("You have left the guild."); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/LockUnlockDoorMsgHandler.java b/src/engine/net/client/handlers/LockUnlockDoorMsgHandler.java new file mode 100644 index 00000000..9269fda6 --- /dev/null +++ b/src/engine/net/client/handlers/LockUnlockDoorMsgHandler.java @@ -0,0 +1,88 @@ +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.LockUnlockDoorMsg; +import engine.objects.Blueprint; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which handle + * lock and unlock door requests to and from the client. + * + */ + +public class LockUnlockDoorMsgHandler extends AbstractClientMsgHandler { + + public LockUnlockDoorMsgHandler() { + super(LockUnlockDoorMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declarations + + PlayerCharacter player; + Building targetBuilding; + int doorNum; + LockUnlockDoorMsg msg; + + // Member variable assignment + + msg = (LockUnlockDoorMsg) baseMsg; + player = SessionManager.getPlayerCharacter(origin); + targetBuilding = BuildingManager.getBuilding((int) msg.getTargetID()); + + if (player == null || targetBuilding == null) { + Logger.warn("Player or Building returned NULL in LockUnlock msg handling."); + return true; + } + + if (player.getLoc().distanceSquared2D(targetBuilding.getLoc()) > MBServerStatics.OPENCLOSEDOORDISTANCE * MBServerStatics.OPENCLOSEDOORDISTANCE) { + return true; + } + + if (!BuildingManager.playerCanManage(player, targetBuilding)) { + return true; + } + + doorNum = Blueprint.getDoorNumberbyMesh(msg.getDoorID()); + + // Debugging code + + // Logger.debug("DoorLockUnlock", "Door mesh: " + msg.getDoorID() + " Door number: " + doorNum); + + boolean stateChanged; + + if (targetBuilding.isDoorLocked(doorNum)) { + stateChanged = targetBuilding.setDoorState(doorNum, engine.Enum.DoorState.UNLOCKED); + } else { + stateChanged = targetBuilding.setDoorState(doorNum, engine.Enum.DoorState.LOCKED); + } + + if (stateChanged == false) { + Logger.error("WorldServerMsgHandler.LockUnlockDoor", "Failed to update db for building: " + targetBuilding.getObjectUUID() + ", door: " + msg.getDoorID()); + } + + if (targetBuilding.isDoorLocked(doorNum)) { + msg.setUnk1(1); // Which is this, locked or unlocaked? + } else { + msg.setUnk1(0); + } + + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + return true; + } +} diff --git a/src/engine/net/client/handlers/LoginToGameServerMsgHandler.java b/src/engine/net/client/handlers/LoginToGameServerMsgHandler.java new file mode 100644 index 00000000..7413ceb4 --- /dev/null +++ b/src/engine/net/client/handlers/LoginToGameServerMsgHandler.java @@ -0,0 +1,124 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.InterestManagement.WorldGrid; +import engine.exception.MsgSendException; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.LoginToGameServerMsg; +import engine.net.client.msg.login.LoginErrorMsg; +import engine.objects.Account; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import engine.session.CSSession; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * logs the character nto the game using the CSession key generated + * by the Login server. + */ + +public class LoginToGameServerMsgHandler extends AbstractClientMsgHandler { + + public LoginToGameServerMsgHandler() { + super(LoginToGameServerMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + LoginToGameServerMsg msg = (LoginToGameServerMsg) baseMsg; + + CSSession sessionInfo = CSSession.getCrossServerSession(msg.getSecKey()); + + if (sessionInfo == null) { + Logger.error("Failed to validate session information from " + origin.getLocalAddressAndPortAsString()); + origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to validate session data"); + // TODO Evaluate if we need to delete CSSessions here. We couldn't + // find it before, why would this attempt be different? + + return true; + } + + Account acc = sessionInfo.getAccount(); + + if (acc == null) { + String err = "Session returned NULL Account. Conn:" + origin.getLocalAddressAndPortAsString(); + Logger.error(err); + origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, err); + return true; + } + + PlayerCharacter pc = sessionInfo.getPlayerCharacter(); + + if (pc == null) { + String err = "Session returned NULL PlayerCharacter. Conn:" + origin.getLocalAddressAndPortAsString(); + + Logger.error(err); + origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, err); + return true; + } + + // If account is suspended, kick + + if (acc.status.equals(Enum.AccountStatus.BANNED)) { + origin.kickToLogin(MBServerStatics.LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT, "Account banned."); + return true; + } + + ClientConnection old = SessionManager.getClientConnection(acc); + + if (old != null) + if (old != origin) { + Logger.info("Disconnecting other client connection Using Same Account " + old.getRemoteAddressAndPortAsString()); + old.disconnect(); + } + + // Set machine ID here from CSS info + origin.machineID = sessionInfo.getMachineID(); + + // Send response + msg.setSecKey(""); + + if (!origin.sendMsg(msg)) { + Logger.error("Failed to send message"); + origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send ValidateGameServer to client."); + return true; + } + + //# Why was this all changed? + // CLEAN UP OTHER INSTANCES OF THIS CHARACTER + + Session toKill = SessionManager.getSession(sessionInfo.getPlayerCharacter()); + + if (toKill != null) { + if (toKill.getConn() != null) { + LoginErrorMsg lom = new LoginErrorMsg(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "You may not login the same character twice!"); + ClientConnection conn = toKill.getConn(); + if (conn != null && !conn.sendMsg(lom)) + Logger.error("Failed to send message"); // TODO Do we just accept this failure to send Msg? + } + SessionManager.remSession(toKill); + WorldGrid.RemoveWorldObject(sessionInfo.getPlayerCharacter()); + } + Session s = SessionManager.getNewSession(sessionInfo.getAccount(), origin); + SessionManager.setPlayerCharacter(s, sessionInfo.getPlayerCharacter()); + + Logger.info("Login from Account: " + sessionInfo.getAccount().getUname() + " Character: " + + sessionInfo.getPlayerCharacter().getName() + " machineID: " + sessionInfo.getMachineID()); + + // Log Admin Login Event + + if (pc.getAccount().status.equals(Enum.AccountStatus.ADMIN)) + DbManager.AccountQueries.WRITE_ADMIN_LOG(pc.getCombinedName(), "ADMIN LOGIN"); + + DbManager.AccountQueries.SET_ACCOUNT_LOGIN(sessionInfo.getAccount(), sessionInfo.getPlayerCharacter().getName(), origin.getClientIpAddress(), sessionInfo.getMachineID()); + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/MOTDCommitHandler.java b/src/engine/net/client/handlers/MOTDCommitHandler.java new file mode 100644 index 00000000..fba1e435 --- /dev/null +++ b/src/engine/net/client/handlers/MOTDCommitHandler.java @@ -0,0 +1,82 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.guild.MOTDCommitMsg; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class MOTDCommitHandler extends AbstractClientMsgHandler { + + public MOTDCommitHandler() { + super(MOTDCommitMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + MOTDCommitMsg msg = (MOTDCommitMsg) baseMsg; + Dispatch dispatch; + + // get source player + PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter( + origin); + + if (sourcePlayer == null) + return true; + + int type = msg.getType(); + + if (type == 0 || type == 1 || type == 3) { + + if (GuildStatusController.isInnerCouncil(sourcePlayer.getGuildStatus()) == false) + return true; + + Guild guild = sourcePlayer.getGuild(); + + if (guild == null) + return true; + + if (type == 1) { // Guild MOTD + guild.setMOTD(msg.getMessage()); + ChatManager.chatGuildMOTD(sourcePlayer, msg.getMessage(), + true); + } else if (type == 3) { // IC MOTD + guild.setICMOTD(msg.getMessage()); + ChatManager + .chatICMOTD(sourcePlayer, msg.getMessage(), true); + } else if (type == 0) { // Nation MOTD + Guild nation = guild.getNation(); + if (nation == null) + return true; + if (nation.isNation()) { // only + // nation's + // primary guild can + // set nation motd + nation.setMOTD(msg.getMessage()); + ChatManager.chatNationMOTD(sourcePlayer, + msg.getMessage(), true); + } + } + dispatch = Dispatch.borrow(sourcePlayer, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } + + return true; + } + +} diff --git a/src/engine/net/client/handlers/MOTDEditHandler.java b/src/engine/net/client/handlers/MOTDEditHandler.java new file mode 100644 index 00000000..a9309021 --- /dev/null +++ b/src/engine/net/client/handlers/MOTDEditHandler.java @@ -0,0 +1,83 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.LeaveGuildMsg; +import engine.net.client.msg.guild.MOTDMsg; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class MOTDEditHandler extends AbstractClientMsgHandler { + + public MOTDEditHandler() { + super(MOTDMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + MOTDMsg msg = (MOTDMsg) baseMsg; + Dispatch dispatch; + + // get source player + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter( + origin); + + if (playerCharacter == null) + return true; + + int type = msg.getType(); + + msg.setResponse((byte) 1); + if (type == 0 || type == 1 || type == 3) { + if (GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(playerCharacter, "You do not have such authority!"); + return true; + } + + Guild guild = playerCharacter.getGuild(); + + if (guild == null || guild.getObjectUUID() == 0) { + + LeaveGuildMsg leaveGuildMsg = new LeaveGuildMsg(); + leaveGuildMsg.setMessage("You do not belong to a guild!"); + dispatch = Dispatch.borrow(playerCharacter, leaveGuildMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + } + + if (type == 1) // Guild MOTD + msg.setMessage(guild.getMOTD()); + else if (type == 3) // IC MOTD + msg.setMessage(guild.getICMOTD()); + else if (type == 0) { // Nation MOTD + Guild nation = guild.getNation(); + if (nation == null || !nation.isNation()) { + ErrorPopupMsg.sendErrorMsg(playerCharacter, "You do not have such authority!"); + return true; + } + msg.setMessage(nation.getMOTD()); + } + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + return true; + } +} diff --git a/src/engine/net/client/handlers/ManageCityAssetMsgHandler.java b/src/engine/net/client/handlers/ManageCityAssetMsgHandler.java new file mode 100644 index 00000000..e2b0af01 --- /dev/null +++ b/src/engine/net/client/handlers/ManageCityAssetMsgHandler.java @@ -0,0 +1,359 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.SessionManager; +import engine.gameManager.ZoneManager; +import engine.math.Bounds; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.ManageCityAssetsMsg; +import engine.net.client.msg.PlaceAssetMsg; +import engine.objects.*; +import org.joda.time.DateTime; + +/* + * @Author: + * @Summary: Processes application protocol message which opens + * and processes the various building asset management windows. + */ +public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler { + + public ManageCityAssetMsgHandler() { + super(ManageCityAssetsMsg.class); + } + + public static boolean playerCanManageNotFriends(PlayerCharacter player, Building building) { + + //Player Can only Control Building if player is in Same Guild as Building and is higher rank than IC. + + if (player == null) + return false; + + if (building.getRank() == -1) + return false; + + if (BuildingManager.IsOwner(building, player)) + return true; + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) + return false; + + //Somehow guild leader check fails above? lets check if Player is true Guild GL. + if (building.getGuild() != null && building.getGuild().isGuildLeader(player.getObjectUUID())) + return true; + + return Guild.sameGuild(building.getGuild(), player.getGuild()); + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + ManageCityAssetsMsg msg; + PlayerCharacter player; + ManageCityAssetsMsg outMsg; + Building building; + + msg = (ManageCityAssetsMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + building = BuildingManager.getBuildingFromCache(msg.getTargetID()); + + if (building == null) { + if (msg.actionType == 14) { + + Zone zone = ZoneManager.findSmallestZone(player.getLoc()); + + if (!zone.isPlayerCity()) { + ErrorPopupMsg.sendErrorMsg(player, "Unable to find city to command."); + return true; + } + + City city = City.GetCityFromCache(zone.getPlayerCityUUID()); + + if (city == null) { + ErrorPopupMsg.sendErrorMsg(player, "Unable to find city to command."); + return true; + } + + if (!city.getGuild().equals(player.getGuild())) { + ErrorPopupMsg.sendErrorMsg(player, "You are not in the correct guild to command this city."); + return true; + } + + if (!GuildStatusController.isInnerCouncil(player.getGuildStatus()) && !GuildStatusController.isGuildLeader(player.getGuildStatus())) { + ErrorPopupMsg.sendErrorMsg(player, "You must be an Inner Council or Guild leader to access city commands."); + return true; + } + ManageCityAssetsMsg mca = new ManageCityAssetsMsg(player, building); + mca.actionType = 15; + Dispatch dispatch = Dispatch.borrow(player, mca); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + return true; + } + + outMsg = new ManageCityAssetsMsg(player, building); + + if (player.isSafeMode()) { + outMsg.actionType = 4; + outMsg.setTargetType(building.getObjectType().ordinal()); + outMsg.setTargetID(building.getObjectUUID()); + outMsg.setAssetName(building.getName()); + Dispatch dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + + if (msg.actionType == 2 || msg.actionType == 22) { + + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == engine.Enum.BuildingGroup.BANESTONE) { + + outMsg.actionType = 18; + outMsg.setTargetType(building.getObjectType().ordinal()); + outMsg.setTargetID(building.getObjectUUID()); + + } else if (BuildingManager.playerCanManage(player, building)) { //TODO allow Friends list. + configWindowState(player, building, outMsg); + outMsg.actionType = 3; + outMsg.setTargetType(building.getObjectType().ordinal()); + outMsg.setTargetID(building.getObjectUUID()); + outMsg.setTargetType3(building.getObjectType().ordinal()); + outMsg.setTargetID3(building.getObjectUUID()); + outMsg.setUnknown54(1); + + } else { + + if (building.getBlueprintUUID() != 0) + switch (building.getBlueprint().getBuildingGroup()) { + case SHRINE: + if (building.getRank() == -1) { + if (!Bounds.collide(player.getLoc(), building)) { + ErrorPopupMsg.sendErrorPopup(player, 64); + return true; + } + + Shrine shrine = Shrine.shrinesByBuildingUUID.get(building.getObjectUUID()); + + if (shrine == null) + return true; + + if (shrine.getFavors() == 0) { + ErrorPopupMsg.sendErrorPopup(player, 166); // There is no more favor in this shrine to loot + return true; + } + + BuildingManager.lootBuilding(player, building); + return true; + } + break; + case WAREHOUSE: + //TODO check + if (building.getRank() == -1) { + if (!Bounds.collide(player.getLoc(), building)) { + ErrorPopupMsg.sendErrorPopup(player, 64); + return true; + } + + Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(building.getObjectUUID()); + + if (warehouse == null) + return true; + + if (warehouse.isEmpty()) { + ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources. + return true; + } + + BuildingManager.lootBuilding(player, building); + return true; + } + } + + if (building.getRank() == -1) + return true; + + AbstractCharacter owner = building.getOwner(); + + //no owner, send building info + if (owner == null) { + msg.actionType = 4; + + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + outMsg.actionType = 4; + outMsg.setTargetType(building.getObjectType().ordinal()); + outMsg.setTargetID(building.getObjectUUID()); + outMsg.setAssetName(building.getName()); + + } + Dispatch dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + + if (msg.actionType == 13) { + outMsg.actionType = 13; + Dispatch dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + + + //Rename Building. + + if (msg.actionType == 5) { + + //TODO we need to check names before allowing + building.setName(msg.getAssetName()); + configWindowState(player, building, outMsg); + + outMsg.actionType = 3; + outMsg.setTargetType(building.getObjectType().ordinal()); + outMsg.setTargetID(building.getObjectUUID()); + outMsg.setTargetType3(GameObjectType.Building.ordinal()); + outMsg.setTargetID3(building.getObjectUUID()); + outMsg.setAssetName1(building.getName()); + outMsg.setUnknown54(1); + + Dispatch dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + return true; + + //TOL, update city name also + //TODO update city and zone in database + //TODO update city map data in game server + } + + if (msg.actionType == 14) { + ManageCityAssetsMsg mca = new ManageCityAssetsMsg(player, building); + mca.actionType = 15; + Dispatch dispatch = Dispatch.borrow(player, mca); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + + if (msg.actionType == 20) { + + Zone baneZone = building.getParentZone(); + + if (baneZone == null) + return true; + + City banedCity = City.getCity(baneZone.getPlayerCityUUID()); + + if (banedCity == null) + return true; + + Bane bane = banedCity.getBane(); + + if (bane == null || bane.getLiveDate() != null || player.getGuild() != banedCity.getGuild() || GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) + return true; + + int baneHour = msg.getBaneHour(); + + if (baneHour < 16 || baneHour > 24) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return true; + } + + DateTime baneLive = new DateTime(bane.getPlacementDate()); + baneLive = baneHour == 24 ? baneLive.plusDays(3) : baneLive.plusDays(2); + baneLive = baneHour == 24 ? baneLive.hourOfDay().setCopy(0) : baneLive.hourOfDay().setCopy(baneHour); + baneLive = baneLive.minuteOfHour().setCopy(0); + baneLive = baneLive.secondOfMinute().setCopy(1); + bane.setLiveDate(baneLive); + outMsg.actionType = 18; + + Dispatch dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + return true; + } + + public void configWindowState(PlayerCharacter player, Building building, ManageCityAssetsMsg manageCityAssetsMsg) { + + // Tests to turn on upgrade button if a building is not + // at it's maximum allowed rank or currently ranking + + + // Owner is obviously allowed to upgrade his own buildings + + if (building.getOwner().equals(player)) { + + // Players cannot destroy or transfer a TOL. + + if (building.getBlueprint() == null) { + manageCityAssetsMsg.buttonDestroy = 0; + manageCityAssetsMsg.buttonTransfer = 0; + manageCityAssetsMsg.buttonAbandon = 0; + manageCityAssetsMsg.buttonUpgrade = 0; + } else if (building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.TOL) { + manageCityAssetsMsg.buttonDestroy = 0; + manageCityAssetsMsg.buttonTransfer = 0; + manageCityAssetsMsg.buttonAbandon = 1; + manageCityAssetsMsg.buttonUpgrade = 1; + } else if (building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.MINE) { + manageCityAssetsMsg.buttonDestroy = 0; + manageCityAssetsMsg.buttonTransfer = 0; + manageCityAssetsMsg.buttonAbandon = 0; + manageCityAssetsMsg.buttonUpgrade = 0; // Cannot upgrade a mine + } else { + manageCityAssetsMsg.buttonDestroy = 1; + manageCityAssetsMsg.buttonTransfer = 1; + manageCityAssetsMsg.buttonAbandon = 1; + manageCityAssetsMsg.buttonUpgrade = 1; + } + } + + // Inner Council of the same guild can also upgrade + + if ((player.getGuild().equals(building.getGuild())) && + GuildStatusController.isInnerCouncil(player.getGuildStatus())) + manageCityAssetsMsg.buttonUpgrade = 1; + + // Disable upgrade button if at max rank. + + if (building.getBlueprint() == null) + manageCityAssetsMsg.buttonUpgrade = 0; + else if (building.getRank() >= building.getBlueprint().getMaxRank()) + manageCityAssetsMsg.buttonUpgrade = 0; + ; + + // If a building is not protected we can exit here + + if (building.assetIsProtected() == false) + return; + + // Protection is displayed as "UNDER SIEGE" if + // an active bane is invalidating the protection + // contracts of the city. + + if ((building.getCity() != null) && + (building.getCity().protectionEnforced == false)) { + manageCityAssetsMsg.labelProtected = 0; + manageCityAssetsMsg.labelSiege = 1; + manageCityAssetsMsg.labelCeaseFire = 0; + return; + } + + // Building is currently protected by a TOL + + manageCityAssetsMsg.labelProtected = 1; + } +} diff --git a/src/engine/net/client/handlers/MerchantMsgHandler.java b/src/engine/net/client/handlers/MerchantMsgHandler.java new file mode 100644 index 00000000..94490a72 --- /dev/null +++ b/src/engine/net/client/handlers/MerchantMsgHandler.java @@ -0,0 +1,454 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GuildHistoryType; +import engine.InterestManagement.RealmMap; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.job.JobScheduler; +import engine.jobs.TeleportJob; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; + +import java.util.ArrayList; + +/* + * @Author: + * @Summary: Processes a variety of windows the client can open + * such as realm blessings and warehouse deposits. + */ + +public class MerchantMsgHandler extends AbstractClientMsgHandler { + + public MerchantMsgHandler() { + super(MerchantMsg.class); + } + + private static void requestSwearAsSubGuild(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, NPC npc) { + + boolean Disabled = true; + + if (Disabled) { + ErrorPopupMsg.sendErrorMsg(player, "Swearing to Safeholds have been temporary disabled."); //Cannot sub as errant guild. + return; + } + + if (player.getGuild().isEmptyGuild()) { + ErrorPopupMsg.sendErrorMsg(player, "You do not belong to a guild!"); //Cannot sub as errant guild. + return; + } + + if (player.getGuild().getNation() != null && !player.getGuild().getNation().isEmptyGuild()) { + ErrorPopupMsg.sendErrorMsg(player, "You already belong to a nation!"); //Cannot sub as errant guild. + return; + } + + if (player.getGuild().getGuildLeaderUUID() != player.getObjectUUID()) { + ErrorPopupMsg.sendErrorMsg(player, "You must be a Guild Leader to Swear your guild as a Sub Guild!"); //Cannot sub as errant guild. + return; + } + + if (!GuildStatusController.isGuildLeader(player.getGuildStatus())) { + ErrorPopupMsg.sendErrorMsg(player, "You must be a Guild Leader to Swear your guild as a Sub Guild!"); //Cannot sub as errant guild. + return; + } + + + if (!npc.getGuild().isNPCGuild()) { + ErrorPopupMsg.sendErrorMsg(player, "Runemaster does not belong to a safehold!"); //Cannot sub as errant guild. + return; + } + + if (npc.getGuild().getRepledgeMin() > player.getLevel()) { + ErrorPopupMsg.sendErrorMsg(player, "You are too low of a level to sub to this guild!"); //Cannot sub as errant guild. + return; + } + + if (npc.getGuild().getRepledgeMax() < 75) { + ErrorPopupMsg.sendErrorMsg(player, "Runemaster Guild Cannot Swear in your guild!"); //Cannot sub as errant guild. + return; + } + + + if (!DbManager.GuildQueries.UPDATE_PARENT(player.getGuild().getObjectUUID(), npc.getGuild().getObjectUUID())) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return; + } + + + GuildManager.updateAllGuildBinds(player.getGuild(), npc.getGuild().getOwnedCity()); + + + //update Guild state. + player.getGuild().setNation(npc.getGuild()); + GuildManager.updateAllGuildTags(player.getGuild()); + + //update state twice, errant to petitioner, to sworn. + player.getGuild().upgradeGuildState(false);//to petitioner + player.getGuild().upgradeGuildState(false);//to sworn + + + } + + private static void requestHermitBlessing(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, NPC npc) { + + Guild guild; + Realm realm; + City city; + Building tol; + + // Validate player can obtain blessing + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorPopup(player, 173); // You must be the leader of a guild to receive a blessing + return; + } + + guild = player.getGuild(); + city = guild.getOwnedCity(); + + if (city == null) { + ErrorPopupMsg.sendErrorPopup(player, 179); // Only landed guilds may claim a territory + return; + } + tol = city.getTOL(); + + if (tol.getRank() != 7) { + ErrorPopupMsg.sendErrorPopup(player, 181); // Your tree must be rank 7 before claiming a territory + return; + } + + realm = RealmMap.getRealmForCity(city); + + if (realm.getCanBeClaimed() == false) { + ErrorPopupMsg.sendErrorPopup(player, 180); // This territory cannot be ruled by anyone + return; + } + + if (realm.isRuled() == true) { + ErrorPopupMsg.sendErrorPopup(player, 178); // This territory is already claimed + return; + } + + // Everything should be good, apply boon for this hermit + + PowersManager.applyPower(player, player, player.getLoc(), getPowerforHermit(npc).getToken(), 40, false); + + } + + private static void requestBoon(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, NPC npc) { + + Building shrineBuilding; + Shrine shrine; + + if (npc.getGuild() != player.getGuild()) + return; + + shrineBuilding = npc.getBuilding(); + + if (shrineBuilding == null) + return; + + if (shrineBuilding.getBlueprint() != null && shrineBuilding.getBlueprint().getBuildingGroup() != engine.Enum.BuildingGroup.SHRINE) + return; + + if (shrineBuilding.getRank() == -1) + return; + + shrine = Shrine.shrinesByBuildingUUID.get(shrineBuilding.getObjectUUID()); + + if (shrine == null) + return; + + if (shrine.getFavors() == 0) { + ErrorPopupMsg.sendErrorPopup(player, 172); + return; + } + + //already haz boon. + + if (player.containsEffect(shrine.getShrineType().getPowerToken())) { + ErrorPopupMsg.sendErrorPopup(player, 199); + return; + } + + if (!Shrine.canTakeFavor(player, shrine)) + return; + + if (!shrine.takeFavor(player)) + return; + + PowersBase shrinePower = PowersManager.getPowerByToken(shrine.getShrineType().getPowerToken()); + + if (shrinePower == null) { + ChatManager.chatSystemError(player, "FAILED TO APPLY POWER!"); + return; + } + + int rank = shrine.getRank(); + //R8 trees always get atleast rank 2 boons. rank uses index, where 0 is first place, 1 is second, etc... + if (shrineBuilding.getCity() != null && shrineBuilding.getCity().getTOL() != null && shrineBuilding.getCity().getTOL().getRank() == 8) + if (rank != 0) + rank = 1; + int trains = 40 - (rank * 10); + if (trains < 0) + trains = 0; + + //System.out.println(trains); + PowersManager.applyPower(player, player, player.getLoc(), shrinePower.getToken(), trains, false); + ChatManager.chatGuildInfo(player.getGuild(), player.getName() + " has recieved a boon costing " + 1 + " point of favor."); + shrineBuilding.addEffectBit(1000000 << 2); + shrineBuilding.updateEffects(); + + //remove the effect so players loading shrines dont see the effect go off. + shrineBuilding.removeEffectBit(1000000 << 2); + } + + private static void teleportRepledgeScreen(MerchantMsg msg, ClientConnection origin, PlayerCharacter pc, boolean isTeleport, NPC npc) { + + Dispatch dispatch; + TeleportRepledgeListMsg trlm; + + //verify npc is runemaster + + Contract contract = npc.getContract(); + + if (contract == null || !contract.isRuneMaster()) + return; + + if (!isTeleport) + trlm = new TeleportRepledgeListMsg(pc, false); + else + trlm = new TeleportRepledgeListMsg(pc, true); + + trlm.configure(); + + dispatch = Dispatch.borrow(pc, trlm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + private static void teleportRepledge(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, boolean isTeleport, NPC npc) { + + //verify npc is runemaster + + Contract contract = npc.getContract(); + Dispatch dispatch; + + if (contract == null || !contract.isRuneMaster()) + return; + + //get city to teleport/repledge to and verify valid + + ArrayList cities; + + City targetCity = null; + + if (isTeleport) + cities = City.getCitiesToTeleportTo(player); + else + cities = City.getCitiesToRepledgeTo(player); + for (City city : cities) { + if (city.getObjectUUID() == msg.getCityID()) { + targetCity = city; + break; + } + } + + if (targetCity == null) + return; + + //verify level required to teleport or repledge + + Guild toGuild = targetCity.getGuild(); + + if (toGuild != null) + if (isTeleport) { + if (player.getLevel() < toGuild.getTeleportMin() || player.getLevel() > toGuild.getTeleportMax()) + return; + } else if (player.getLevel() < toGuild.getRepledgeMin() || player.getLevel() > toGuild.getRepledgeMax()) + return; + + boolean joinedGuild = false; + + //if repledge, reguild the player + + if (!isTeleport) + joinedGuild = GuildManager.joinGuild(player, targetCity.getGuild(), targetCity.getObjectUUID(), GuildHistoryType.JOIN); + + int time; + + if (!isTeleport) //repledge + time = MBServerStatics.REPLEDGE_TIME_IN_SECONDS; + else + time = MBServerStatics.TELEPORT_TIME_IN_SECONDS; + + //resend message + msg.setTeleportTime(time); + + if ((!isTeleport && joinedGuild) || (isTeleport)) { + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + //teleport player to city + + Vector3fImmutable teleportLoc; + + if (targetCity.getTOL().getRank() == 8) + teleportLoc = targetCity.getTOL().getStuckLocation(); + else + teleportLoc = Vector3fImmutable.getRandomPointOnCircle(targetCity.getTOL().getLoc(), MBServerStatics.TREE_TELEPORT_RADIUS); + + if (time > 0) { + //TODO add timer to teleport + TeleportJob tj = new TeleportJob(player, npc, teleportLoc, origin, true); + JobScheduler.getInstance().scheduleJob(tj, time * 1000); + } else if (joinedGuild) { + player.teleport(teleportLoc); + player.setSafeMode(); + } + } + + private static PowersBase getPowerforHermit(NPC npc) { + + int contractID; + PowersBase power; + Contract contract; + + contract = npc.getContract(); + contractID = contract.getContractID(); + power = null; + + switch (contractID) { + case 435579: + power = PowersManager.getPowerByIDString("BLS-POWER"); + break; + case 435580: + power = PowersManager.getPowerByIDString("BLS-FORTUNE"); + break; + case 435581: + power = PowersManager.getPowerByIDString("BLS-WISDOM"); + break; + + } + return power; + } + + private static boolean isHermit(NPC npc) { + + int contractID; + boolean retValue = false; + + contractID = npc.getContractID(); + + switch (contractID) { + case 435579: + case 435580: + case 435581: + retValue = true; + break; + default: + break; + } + + return retValue; + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + MerchantMsg msg; + PlayerCharacter player; + NPC npc; + int msgType; + Building warehouse; + Dispatch dispatch; + + // Member variable assignment + + player = SessionManager.getPlayerCharacter(origin); + msg = (MerchantMsg) baseMsg; + npc = NPC.getNPC(msg.getNPCID()); + + // Early exit if something goes awry + + if ((player == null) || (npc == null)) + return true; + + // Player must be within talking range + + if (player.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { + ErrorPopupMsg.sendErrorPopup(player, 14); + return true; + } + + // Process application protocol message + + msgType = msg.getType(); + + switch (msgType) { + case 3: + break; + case 5: + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + requestSwearAsSubGuild(msg, origin, player, npc); + break; + case 10: + teleportRepledgeScreen(msg, origin, player, false, npc); + break; + case 11: + teleportRepledge(msg, origin, player, false, npc); + break; + case 12: + teleportRepledgeScreen(msg, origin, player, true, npc); + break; + case 13: + teleportRepledge(msg, origin, player, true, npc); + break; + case 14: + if (isHermit(npc)) + requestHermitBlessing(msg, origin, player, npc); + else + requestBoon(msg, origin, player, npc); + break; + case 15: + LeaderboardMessage lbm = new LeaderboardMessage(); + dispatch = Dispatch.borrow(player, lbm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + case 16: + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + warehouse = npc.getBuilding(); + vrm.setGuild(player.getGuild()); + vrm.setWarehouseBuilding(warehouse); + vrm.configure(); + dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + case 17: + Warehouse.warehouseWithdraw(msg, player, npc, origin); + break; + case 18: + Warehouse.warehouseDeposit(msg, player, npc, origin); + break; + case 19: + Warehouse.warehouseLock(msg, player, npc, origin); + break; + } + + return true; + + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/MineWindowChangeHandler.java b/src/engine/net/client/handlers/MineWindowChangeHandler.java new file mode 100644 index 00000000..5118088c --- /dev/null +++ b/src/engine/net/client/handlers/MineWindowChangeHandler.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ArcMineWindowChangeMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.objects.Building; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.time.LocalDateTime; + +/* + * @Author: + * @Summary: Processes requests to change a mine's opendate + */ + +public class MineWindowChangeHandler extends AbstractClientMsgHandler { + + public MineWindowChangeHandler() { + super(ArcMineWindowChangeMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin); + ArcMineWindowChangeMsg mineWindowChangeMsg = (ArcMineWindowChangeMsg) baseMsg; + int newMineTime; + + if (playerCharacter == null) + return true; + + Building treeOfLife = BuildingManager.getBuildingFromCache(mineWindowChangeMsg.getBuildingID()); + + if (treeOfLife == null) + return true; + + if (treeOfLife.getBlueprintUUID() == 0) + return true; + + if (treeOfLife.getBlueprint().getBuildingGroup() != Enum.BuildingGroup.TOL) + return true; + + Guild mineGuild = treeOfLife.getGuild(); + + if (mineGuild.isEmptyGuild()) + return true; + + if (!Guild.sameGuild(mineGuild, playerCharacter.getGuild())) + return true; //must be same guild + + if (GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()) == false) // is this only GL? + return true; + + newMineTime = mineWindowChangeMsg.getTime(); + + // Sanity check for possible slider value + + if (newMineTime == 24) + newMineTime = 0; + + // Enforce time restriction between WOO edits + + if (mineGuild.wooWasModified) { + ErrorPopupMsg.sendErrorMsg(playerCharacter, "You can only modify your WOO once per day."); + return true; + } + + //hodge podge sanity check to make sure they don't set it before early window and is not set at late window. + + if (newMineTime < MBServerStatics.MINE_EARLY_WINDOW && + newMineTime != MBServerStatics.MINE_LATE_WINDOW) { + ErrorPopupMsg.sendErrorMsg(playerCharacter, "Mine time is outside the NA Woo window."); + return true; + } + + // Cannot set a time to a window that has closed if mines are currently open. + + if (LocalDateTime.now().getHour() >= MBServerStatics.MINE_EARLY_WINDOW && + LocalDateTime.now().getHour() != MBServerStatics.MINE_LATE_WINDOW) { + + if (newMineTime <= LocalDateTime.now().getHour()) + ErrorPopupMsg.sendErrorMsg(playerCharacter, "Cannot set mines to a previous window."); + return true; + } + + // Update guild mine time + + if (!DbManager.GuildQueries.UPDATE_MINETIME(mineGuild.getObjectUUID(), newMineTime)) { + Logger.error("MineWindowChange", "Failed to update mine time for guild " + mineGuild.getObjectUUID()); + ChatManager.chatGuildError(playerCharacter, "Failed to update the mine time"); + return true; + } + + mineGuild.setMineTime(newMineTime); + mineGuild.wooWasModified = true; + + ChatManager.chatGuildInfo(playerCharacter, "Mine time updated."); + + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/MinionTrainingMsgHandler.java b/src/engine/net/client/handlers/MinionTrainingMsgHandler.java new file mode 100644 index 00000000..a71a9300 --- /dev/null +++ b/src/engine/net/client/handlers/MinionTrainingMsgHandler.java @@ -0,0 +1,332 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.InterestManagement.WorldGrid; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.NPCManager; +import engine.gameManager.SessionManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashMap; + +/* + * @Author: + * @Summary: Processes application protocol message which + * processes training of minions in guard barracks + */ + +public class MinionTrainingMsgHandler extends AbstractClientMsgHandler { + + public static HashMap> _minionsByCaptain = null; + + public MinionTrainingMsgHandler() { + super(MinionTrainingMessage.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + MinionTrainingMessage minionMsg = (MinionTrainingMessage) baseMsg; + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + if (minionMsg.getNpcType() == Enum.GameObjectType.NPC.ordinal()) { + + NPC npc = NPC.getFromCache(minionMsg.getNpcID()); + + if (npc == null) + return true; + + Building b = BuildingManager.getBuildingFromCache(minionMsg.getBuildingID()); + + if (b == null) + return true; + + //clear minion + + if (npc.minionLock.writeLock().tryLock()) { + try { + if (minionMsg.getType() == 2) { + + Mob toRemove = Mob.getFromCache(minionMsg.getUUID()); + + if (!npc.getSiegeMinionMap().containsKey(toRemove)) + return true; + + npc.getSiegeMinionMap().remove(toRemove); + + WorldGrid.RemoveWorldObject(toRemove); + + if (toRemove.getParentZone() != null) + toRemove.getParentZone().zoneMobSet.remove(toRemove); + + DbManager.removeFromCache(toRemove); + PlayerCharacter petOwner = toRemove.getOwner(); + + if (petOwner != null) { + petOwner.setPet(null); + toRemove.setOwner(null); + PetMsg petMsg = new PetMsg(5, null); + Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + // we Found the move to remove, lets break the for loop so it doesnt look for more. + + ManageCityAssetsMsg mca1 = new ManageCityAssetsMsg(player, b); + mca1.actionType = 3; + mca1.setTargetType(b.getObjectType().ordinal()); + mca1.setTargetID(b.getObjectUUID()); + + mca1.setTargetType3(npc.getObjectType().ordinal()); + mca1.setTargetID3(npc.getObjectUUID()); + mca1.setAssetName1(b.getName()); + mca1.setUnknown54(1); + + Dispatch dispatch = Dispatch.borrow(player, mca1); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ManageNPCMsg mnm = new ManageNPCMsg(npc); + dispatch = Dispatch.borrow(player, mnm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + //Add Minion + } else { + Zone zone = npc.getParentZone(); + + if (zone == null) + return true; + + int maxSlots = 3; + + if (npc.getContractID() == 842) + maxSlots = 1; + + if (npc.getSiegeMinionMap().size() == maxSlots) + return true; + + int mobBase; + + switch (minionMsg.getMinion()) { + case 9: + mobBase = 13171; + break; + case 2: + mobBase = 13758; + break; + case 3: + mobBase = 13757; + break; + case 4: + mobBase = 2111; + break; + case 5: + mobBase = 12402; + break; + case 6: + mobBase = 2113; + break; + default: + mobBase = minionMsg.getMinion(); + } + + if (mobBase == 0) + return true; + + Mob siegeMob = Mob.createSiegeMob(npc, mobBase, npc.getGuild(), zone, b.getLoc(), (short) 1); + + if (siegeMob == null) + return true; + + if (siegeMob != null) { + + siegeMob.setSpawnTime(60 * 15); + Building building = BuildingManager.getBuilding(((MinionTrainingMessage) baseMsg).getBuildingID()); + + siegeMob.building = building; + siegeMob.parentZone = zone; + + // Slot siege minion + // Can be either corner tower or bulwark. + + int slot; + + if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.ARTYTOWER)) + slot = 2; + else + slot = ((NPC) siegeMob.npcOwner).getSiegeMinionMap().get(siegeMob) + 1; // First slot is for the captain + + BuildingLocation slotLocation = BuildingManager._slotLocations.get(building.meshUUID).get(slot); + siegeMob.bindLoc = building.getLoc().add(slotLocation.getLocation()); + + // Rotate slot position by the building rotation + + siegeMob.bindLoc = Vector3fImmutable.rotateAroundPoint(building.getLoc(), siegeMob.bindLoc, building.getBounds().getQuaternion().angleY); + + siegeMob.loc = new Vector3fImmutable(siegeMob.bindLoc); + siegeMob.endLoc = new Vector3fImmutable(siegeMob.bindLoc); + + zone.zoneMobSet.add(siegeMob); + siegeMob.setLoc(siegeMob.bindLoc); + } + } + + ManageNPCMsg mnm = new ManageNPCMsg(npc); + mnm.setMessageType(1); + Dispatch dispatch = Dispatch.borrow(player, mnm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } finally { + npc.minionLock.writeLock().unlock(); + } + } + + } else if (minionMsg.getNpcType() == Enum.GameObjectType.Mob.ordinal()) { + + Mob npc = Mob.getFromCache(minionMsg.getNpcID()); + + if (npc == null) + return true; + + Building building = BuildingManager.getBuildingFromCache(minionMsg.getBuildingID()); + + if (building == null) + return true; + + //clear minion + + if (npc.minionLock.writeLock().tryLock()) { + try { + if (minionMsg.getType() == 2) { + + Mob toRemove = Mob.getFromCache(minionMsg.getUUID()); + + if (!npc.getSiegeMinionMap().containsKey(toRemove)) + return true; + + if (!DbManager.MobQueries.REMOVE_FROM_GUARDS(npc.getObjectUUID(), toRemove.getMobBaseID(), npc.getSiegeMinionMap().get(toRemove))) + return true; + + npc.getSiegeMinionMap().remove(toRemove); + + WorldGrid.RemoveWorldObject(toRemove); + + if (toRemove.getParentZone() != null) + toRemove.getParentZone().zoneMobSet.remove(toRemove); + + DbManager.removeFromCache(toRemove); + PlayerCharacter petOwner = toRemove.getOwner(); + + if (petOwner != null) { + petOwner.setPet(null); + toRemove.setOwner(null); + PetMsg petMsg = new PetMsg(5, null); + Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + ManageCityAssetsMsg mca1 = new ManageCityAssetsMsg(player, building); + mca1.actionType = 3; + mca1.setTargetType(building.getObjectType().ordinal()); + mca1.setTargetID(building.getObjectUUID()); + + mca1.setTargetType3(npc.getObjectType().ordinal()); + mca1.setTargetID3(npc.getObjectUUID()); + mca1.setAssetName1(building.getName()); + mca1.setUnknown54(1); + + Dispatch dispatch = Dispatch.borrow(player, mca1); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ManageNPCMsg mnm = new ManageNPCMsg(npc); + dispatch = Dispatch.borrow(player, mnm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + //Add Minion + } else { + Zone zone = npc.getParentZone(); + + if (zone == null) + return true; + + int maxSlots = 5; + + if (npc.getContract().getContractID() == 842)//artillery captain + maxSlots = 1; + if (npc.getContract().getContractID() == 910)//guard dogs + maxSlots = 0; + switch (npc.getRank()) { + case 1: + case 2: + maxSlots = 1; + break; + case 3: + maxSlots = 2; + break; + case 4: + case 5: + maxSlots = 3; + break; + case 6: + maxSlots = 4; + break; + case 7: + maxSlots = 5; + break; + } + + if (npc.getSiegeMinionMap().size() == maxSlots) + return true; + + int mobBase = npc.getMobBaseID(); + + if (mobBase == 0) + return true; + + String pirateName = NPCManager.getPirateName(mobBase); + + if (!DbManager.MobQueries.ADD_TO_GUARDS(npc.getObjectUUID(), mobBase, pirateName, npc.getSiegeMinionMap().size() + 1)) + return true; + + Mob toCreate = Mob.createGuardMob(npc, npc.getGuild(), zone, building.getLoc(), npc.getLevel(), pirateName); + + if (toCreate == null) + return true; + + if (toCreate != null) { + toCreate.setDeathTime(System.currentTimeMillis()); + toCreate.parentZone.zoneMobSet.add(toCreate); + } + } + + ManageNPCMsg mnm = new ManageNPCMsg(npc); + mnm.setMessageType(1); + Dispatch dispatch = Dispatch.borrow(player, mnm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } catch (Exception e) { + Logger.error(e); + } finally { + + npc.minionLock.writeLock().unlock(); + } + } + + } + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/MoveToPointHandler.java b/src/engine/net/client/handlers/MoveToPointHandler.java new file mode 100644 index 00000000..3c82d07b --- /dev/null +++ b/src/engine/net/client/handlers/MoveToPointHandler.java @@ -0,0 +1,38 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.MovementManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.MoveToPointMsg; +import engine.objects.PlayerCharacter; + +public class MoveToPointHandler extends AbstractClientMsgHandler { + + public MoveToPointHandler() { + super(MoveToPointMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + MoveToPointMsg msg = (MoveToPointMsg) baseMsg; + + PlayerCharacter pc = (origin != null) ? (origin.getPlayerCharacter()) : null; + if (pc == null) + return false; + + MovementManager.movement(msg, pc); + return true; + } + +} diff --git a/src/engine/net/client/handlers/ObjectActionMsgHandler.java b/src/engine/net/client/handlers/ObjectActionMsgHandler.java new file mode 100644 index 00000000..d6ef538e --- /dev/null +++ b/src/engine/net/client/handlers/ObjectActionMsgHandler.java @@ -0,0 +1,564 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.DispatchChannel; +import engine.Enum.ItemType; +import engine.InterestManagement.RealmMap; +import engine.InterestManagement.WorldGrid; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.math.Bounds; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/* + * @Author: + * @Summary: Processes application protocol message which actives + * items such as charters and deeds in the character's inventory + */ +public class ObjectActionMsgHandler extends AbstractClientMsgHandler { + + // Reentrant lock for dropping banes + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + public ObjectActionMsgHandler() { + super(ObjectActionMsg.class); + } + + private static boolean claimRealm(PlayerCharacter player, int charterUUID) { + + Guild guild; + Realm realm; + City city; + Building tol; + float hPMod; + Warehouse warehouse; + boolean hasResources = true; + int resourceValue; + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorPopup(player, 176); // Only guild leaders can claim a territory + return false; + } + + guild = player.getGuild(); + city = guild.getOwnedCity(); + + if (city == null) { + ErrorPopupMsg.sendErrorPopup(player, 179); // Only landed guilds may claim a territory + return false; + } + + if (city.isLocationOnCityGrid(player.getLoc()) == false) { + ErrorPopupMsg.sendErrorPopup(player, 186); // Your tree is not inside a territory! + return false; + } + + tol = city.getTOL(); + + if (tol.getRank() != 7) { + ErrorPopupMsg.sendErrorPopup(player, 181); // Your tree must be rank 7 before claiming a territory + return false; + } + + realm = RealmMap.getRealmForCity(city); + + if (realm.getCanBeClaimed() == false) { + ErrorPopupMsg.sendErrorPopup(player, 180); // This territory cannot be ruled by anyone + return false; + } + + if (realm.isRuled() == true) { + ErrorPopupMsg.sendErrorPopup(player, 178); // This territory is already claimed + return false; + } + + if (!Realm.HasAllBlessings(player)) { + ErrorPopupMsg.sendErrorPopup(player, 185); // You must seek the blessing of the three sages before you can rule + return false; + } + + // Must have the required resources in warehouse to claim realm + + warehouse = city.getWarehouse(); + + if (warehouse == null) { + ErrorPopupMsg.sendErrorPopup(player, 188); // You must have a warehouse to become a capital + return false; + } + + resourceValue = warehouse.getResources().get(Warehouse.goldIB); + + if (resourceValue < 5000000) + hasResources = false; + + resourceValue = warehouse.getResources().get(Warehouse.stoneIB); + + if (resourceValue < 8000) + hasResources = false; + + resourceValue = warehouse.getResources().get(Warehouse.lumberIB); + + if (resourceValue < 8000) + hasResources = false; + + resourceValue = warehouse.getResources().get(Warehouse.galvorIB); + + if (resourceValue < 15) + hasResources = false; + + resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB); + + if (resourceValue < 15) + hasResources = false; + + if (hasResources == false) { + ErrorPopupMsg.sendErrorPopup(player, 184); // Insufficient gold or resources to upgrade to capital + return false; + } + + // Remove resources from warehouse before claiming realm + + resourceValue = warehouse.getResources().get(Warehouse.goldIB); + + if (DbManager.WarehouseQueries.updateGold(warehouse, resourceValue - 5000000) == true) { + warehouse.getResources().put(Warehouse.goldIB, resourceValue - 5000000); + warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GOLD, 5000000); + } else { + Logger.error("gold update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return false; + } + + resourceValue = warehouse.getResources().get(Warehouse.stoneIB); + + if (DbManager.WarehouseQueries.updateStone(warehouse, resourceValue - 8000) == true) { + warehouse.getResources().put(Warehouse.stoneIB, resourceValue - 8000); + warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.STONE, 8000); + } else { + Logger.error("stone update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return false; + } + + resourceValue = warehouse.getResources().get(Warehouse.lumberIB); + + if (DbManager.WarehouseQueries.updateLumber(warehouse, resourceValue - 8000) == true) { + warehouse.getResources().put(Warehouse.lumberIB, resourceValue - 8000); + warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.LUMBER, 8000); + } else { + Logger.error("lumber update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return false; + } + + resourceValue = warehouse.getResources().get(Warehouse.galvorIB); + + if (DbManager.WarehouseQueries.updateGalvor(warehouse, resourceValue - 15) == true) { + warehouse.getResources().put(Warehouse.galvorIB, resourceValue - 15); + warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GALVOR, 15); + } else { + Logger.error("galvor update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return false; + } + + resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB); + + if (DbManager.WarehouseQueries.updateWormwood(warehouse, resourceValue - 15) == true) { + warehouse.getResources().put(Warehouse.wormwoodIB, resourceValue - 15); + warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.WORMWOOD, 15); + } else { + Logger.error("wormwood update failed for warehouse of UUID:" + warehouse.getObjectUUID()); + return false; + } + + realm.claimRealmForCity(city, charterUUID); + + tol.setRank(8); + WorldGrid.updateObject(tol); + + for (Building building : city.getParent().zoneBuildingSet) { + + if (building.getBlueprintUUID() != 0) { + + // TOL Health set through regular linear equation + if (building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) { + continue; + } + + hPMod = (building.getMaxHitPoints() * Realm.getRealmHealthMod(city)); + building.setMaxHitPoints(building.getMaxHitPoints() + hPMod); + } + } + + if (!guild.getNation().equals(guild)) { + guild.getNation().setRealmsOwned(guild.getNation().getRealmsOwned() + 1); + GuildManager.updateAllGuildTags(guild.getNation()); + } + + guild.setRealmsOwned(guild.getRealmsOwned() + 1); + GuildManager.updateAllGuildTags(guild); + + removeAllBlessings(player); + + return true; + + } + + private static void removeAllBlessings(PlayerCharacter player) { + + PowersBase[] powers = new PowersBase[3]; + + powers[0] = PowersManager.getPowerByIDString("BLS-POWER"); + powers[1] = PowersManager.getPowerByIDString("BLS-FORTUNE"); + powers[2] = PowersManager.getPowerByIDString("BLS-WISDOM"); + + for (PowersBase power : powers) { + PowersManager.removeEffect(player, power.getActions().get(0), true, false); + } + + } + + private static void removeRune(PlayerCharacter pc, ClientConnection origin, int runeID) { + + if (pc == null || origin == null) { + return; + } + + //remove only if rune is discipline + if (runeID < 3001 || runeID > 3048) { + return; + } + + //see if pc has rune + ArrayList runes = pc.getRunes(); + + if (runes == null) + return; + + CharacterRune found = pc.getRune(runeID); + + if (found == null) + return; + + //TODO see if player needs to refine skills or powers first + //attempt remove rune from player + + if (!CharacterRune.removeRune(pc, runeID)) + return; + + //update client with removed rune. + ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), runeID); + Dispatch dispatch = Dispatch.borrow(pc, arm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + // Handle activation of tears of seadron: Removes rune from player. + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + ObjectActionMsg msg; + PlayerCharacter player; + CharacterItemManager itemMan; + ArrayList comps; + Dispatch dispatch; + boolean waterbucketBypass = false; + + // Member variable assignment + msg = (ObjectActionMsg) baseMsg; + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) { + return true; + } + + itemMan = player.getCharItemManager(); + + if (itemMan == null) { + return true; + } + + comps = msg.getTargetCompID(); + + if (comps.isEmpty()) { + return true; + } + + long comp = comps.get(0); + + if (((int) comp) != 0) { + Item item = Item.getFromCache((int) comp); + + if (item == null) { + return true; + } + + //dupe check + if (!item.validForInventory(origin, player, itemMan)) { + return true; + } + + ItemBase ib = item.getItemBase(); + + if (ib == null) { + return true; + } + + if (itemMan.doesCharOwnThisItem(item.getObjectUUID())) { + + if (ib.isConsumable() || ib.getType() == ItemType.FARMABLE) { + + int uuid = ib.getUUID(); + int type = ib.getType().getValue(); + + switch (type) { + case 27: //Mithril repair + break; + case 10: //charters + //don't think they're handled here? + break; + case 19: //buildings + //Call add building screen here, ib.getUseID() get's building ID + + //if inside player city, center loc on tol. otherwise center on player. + Vector3fImmutable loc = player.getLoc(); + Zone zone = ZoneManager.findSmallestZone(player.getLoc()); + + if (zone != null) { + if (zone.isPlayerCity()) { + loc = zone.getLoc(); + } + } + + PlaceAssetMsg pam = new PlaceAssetMsg(); + pam.setActionType(2); + pam.setContractID(item.getObjectUUID()); + pam.setX(loc.getX() + 64); //offset grid from tol + pam.setY(loc.getY()); + pam.setZ(loc.getZ() + 64); //offset grid from tol + pam.addPlacementInfo(ib.getUseID()); + + dispatch = Dispatch.borrow(player, pam); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + //itemMan.consume(item); //temporary fix for dupe.. TODO Make Item Unusable after This message is sent. + break; + case 25: //furniture + //Call add furniture screen here. ib.getUseID() get's furniture ID + break; + case 33: + long shrineCompID = comps.get(1); + Building shrineBuilding = BuildingManager.getBuilding((int) shrineCompID); + if (shrineBuilding == null) { + return true; + } + if (shrineBuilding.getBlueprint() != null && shrineBuilding.getBlueprint().getBuildingGroup() != engine.Enum.BuildingGroup.SHRINE) { + return true; + } + + if (shrineBuilding.getRank() == -1) { + return true; + } + Shrine shrine = Shrine.shrinesByBuildingUUID.get(shrineBuilding.getObjectUUID()); + + if (shrine == null) { + return true; + } + + if (shrine.addFavor(player, item)) { + shrineBuilding.addEffectBit(1000000 << 2); + shrineBuilding.updateEffects(); + shrineBuilding.removeEffectBit(1000000 << 2); + } + break; + + case 35: + int charterType = 0; + switch (uuid) { + case 910020: + charterType = 762228431; + break; + case 910021: + charterType = -15978914; + break; + case 910022: + charterType = -600065291; + break; + } + if (claimRealm(player, charterType) == true) { + itemMan.consume(item); + } + break; + case 7: //rod of command + long compID = comps.get(1); + + int objectType = AbstractWorldObject.extractTypeID(compID).ordinal(); + Mob toCommand; + if (objectType == engine.Enum.GameObjectType.Mob.ordinal()) { + toCommand = Mob.getFromCache((int) compID); + } //Only Command Mob Types. + else { + return true; + } + + if (toCommand == null) { + return true; + } + + if (!toCommand.isSiege()) + return true; + + if (player.commandSiegeMinion(toCommand)) { + itemMan.consume(item); + } + break; + //ANNIVERSERY GIFT + case 31: + // *** Disabled for now: Needs bootyset created + + //if (ib.getUUID() == 971012) { + // int random = ThreadLocalRandom.current().nextInt(ItemBase.AnniverseryGifts.size()); + // int annyID = ItemBase.AnniverseryGifts.get(random); + + // ItemBase annyIB = ItemBase.getItemBase(annyID); + // if (annyIB != null) { + // Item gift = MobLoot.createItemForPlayer(player, annyIB); + // if (gift != null) { + // itemMan.addItemToInventory(gift); + // itemMan.consume(item); + // } + // } + // break; + //} + + LootManager.peddleFate(player,item); + break; + + case 30: //water bucket + case 8: //potions, tears of saedron + + case 5: //runes, petition, warrant, scrolls + if (uuid > 3000 && uuid < 3050) { //Discipline Runes + if (ApplyRuneMsg.applyRune(uuid, origin, player)) { + itemMan.consume(item); + } + break; + } else if (uuid > 249999 && uuid < 250123) { //stat and mastery runes + if (ApplyRuneMsg.applyRune(uuid, origin, player)) { + itemMan.consume(item); + } + break; + } else if (uuid > 250114 && uuid < 250123) { //mastery runes + if (ApplyRuneMsg.applyRune(uuid, origin, player)) { + itemMan.consume(item); + } + break; + } else if (uuid > 252122 && uuid < 252128) { //mastery runes + if (ApplyRuneMsg.applyRune(uuid, origin, player)) { + itemMan.consume(item); + } + break; + } else if (uuid > 680069 && uuid < 680074) //Handle Charter, Deed, Petition, Warrant here + { + break; + } else if (uuid > 910010 && uuid < 910019) { + + int rank = uuid - 910010; + + if (rank < 1 || rank > 8) { + ChatManager.chatSystemError(player, "Invalid Rank for bane scroll!"); + return true; + } + // Only one banestone at a time + lock.writeLock().lock(); + + try { + if (Bane.summonBanestone(player, origin, rank) == true) + itemMan.consume(item); + } finally { + lock.writeLock().unlock(); + } + break; + } else if (uuid == 910010) { //tears of saedron + if (comps.size() > 1) { + removeRune(player, origin, comps.get(1).intValue()); + } + break; + } else if (item.getChargesRemaining() > 0) { + ArrayList tarList = msg.getTargetCompID(); + AbstractWorldObject target = player; + if (tarList.size() > 1) { + long tarID = tarList.get(1); + if (tarID != 0) { + AbstractGameObject tarAgo = AbstractGameObject.getFromTypeAndID(tarID); + if (tarAgo != null && tarAgo instanceof AbstractWorldObject) { + target = (AbstractWorldObject) tarAgo; + } + } + } + + // Bypass for waterbuckets + + // test character targeted + + if (ib.getUUID() == 910005) { + + // test for valid target type + if (target.getObjectType() == Enum.GameObjectType.PlayerCharacter) + waterbucketBypass = true; + else { + // test distance to structure + Building targetBuilding = (Building) target; + Bounds testBounds = Bounds.borrow(); + testBounds.setBounds(player.getLoc(), 25); + + if (Bounds.collide(targetBuilding.getBounds(), testBounds, .1f) == false) { + ChatManager.chatSystemError(player, "Not in range of structura for to heal!"); + return true; + } + } + + // Send piss bucket animation + + VisualUpdateMessage vum = new VisualUpdateMessage(player, 16323); + vum.configure(); + DispatchMessage.sendToAllInRange(player, vum); + } + + if (waterbucketBypass == false) + PowersManager.applyPower(player, target, Vector3fImmutable.ZERO, ib.getUseID(), ib.getUseAmount(), true); + + itemMan.consume(item); + } else //just remove the item at this point + itemMan.consume(item); + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + player.cancelOnSpell(); + break; + default: //shouldn't be here, consume item + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + // itemMan.consume(item); + } + } + } else { + // TODO log item does not belong to player + // System.out.println("Item does not belong to player"); + // Cleanup duped item here + } + } + + return true; + } + +} diff --git a/src/engine/net/client/handlers/OpenFriendsCondemnListMsgHandler.java b/src/engine/net/client/handlers/OpenFriendsCondemnListMsgHandler.java new file mode 100644 index 00000000..ccb93609 --- /dev/null +++ b/src/engine/net/client/handlers/OpenFriendsCondemnListMsgHandler.java @@ -0,0 +1,396 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.OpenFriendsCondemnListMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * client requests for various lists + */ + +public class OpenFriendsCondemnListMsgHandler extends AbstractClientMsgHandler { + + public OpenFriendsCondemnListMsgHandler() { + super(OpenFriendsCondemnListMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = SessionManager.getPlayerCharacter(origin); + Building sourceBuilding; + OpenFriendsCondemnListMsg msg; + OpenFriendsCondemnListMsg openFriendsCondemnListMsg; + Enum.FriendListType friendListType; + Dispatch dispatch; + + if (player == null) + return true; + + msg = (OpenFriendsCondemnListMsg) baseMsg; + openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(msg); + friendListType = Enum.FriendListType.getListTypeByID(msg.getMessageType()); + + if (friendListType == null) { + Logger.error("Invalid FriendListType for messageType " + msg.getMessageType()); + return true; + } + + switch (friendListType) { + case VIEWHERALDRY: // Heraldry + + Heraldry.ValidateHeraldry(player.getObjectUUID()); + OpenFriendsCondemnListMsg outMsg = new OpenFriendsCondemnListMsg(msg); + outMsg.setOrigin(origin); + outMsg.setMessageType(2); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + break; + + case ADDHERALDRY: + Heraldry.ValidateHeraldry(player.getObjectUUID()); + if (msg.getPlayerID() <= 0) { + //ErrorPopupMsg.sendErrorMsg(player, "Invalid Heraldry Object."); + return true; + } + AbstractCharacter toAdd = null; + if (msg.getPlayerType() == GameObjectType.PlayerCharacter.ordinal()) + toAdd = PlayerCharacter.getFromCache(msg.getPlayerID()); + else if (msg.getPlayerType() == GameObjectType.NPC.ordinal()) + toAdd = NPC.getFromCache(msg.getPlayerID()); + else if (msg.getPlayerType() == GameObjectType.Mob.ordinal()) + toAdd = Mob.getFromCache(msg.getPlayerID()); + else { + ErrorPopupMsg.sendErrorMsg(player, "Invalid Heraldry Object."); + return true; + } + + if (toAdd == null) { + ErrorPopupMsg.sendErrorMsg(player, "Invalid Heraldry Object."); + return true; + } + + Heraldry.AddToHeraldy(player.getObjectUUID(), toAdd); + + + outMsg = new OpenFriendsCondemnListMsg(msg); + outMsg.setOrigin(origin); + outMsg.setMessageType(2); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + break; + case REMOVEHERALDRY: + Heraldry.ValidateHeraldry(player.getObjectUUID()); + Heraldry.RemoveFromHeraldy(player.getObjectUUID(), msg.getPlayerID()); + + + outMsg = new OpenFriendsCondemnListMsg(msg); + outMsg.setOrigin(origin); + outMsg.setMessageType(2); + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + break; + + case DEALTHS: // Death List + openFriendsCondemnListMsg.updateMsg(8, new ArrayList<>(player.pvpDeaths)); + dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + + case KILLS: // Kill List + openFriendsCondemnListMsg.updateMsg(10, new ArrayList<>(player.pvpKills)); + dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + + case VIEWCONDEMN: + + sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (sourceBuilding == null) + return true; + + openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(12, sourceBuilding.getCondemned(), sourceBuilding.reverseKOS); + openFriendsCondemnListMsg.configure(); + dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + //msg.updateMsg(12, DbManager.GuildQueries.) + break; + //REMOVE CONDEMN + case REMOVECONDEMN: + + sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (sourceBuilding == null) + return true; + + if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player)) + return true; + + Condemned removeCondemn = sourceBuilding.getCondemned().get(msg.getRemoveFriendID()); + + if (removeCondemn == null) + return true; + + if (!DbManager.BuildingQueries.REMOVE_FROM_CONDEMNED_LIST(removeCondemn.getParent(), removeCondemn.getPlayerUID(), removeCondemn.getGuildUID(), removeCondemn.getFriendType())) + return true; + + sourceBuilding.getCondemned().remove(msg.getRemoveFriendID()); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + + case TOGGLEACTIVE: + + sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (sourceBuilding == null) + return true; + + if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player)) + return true; + + Condemned condemn = sourceBuilding.getCondemned().get(msg.getRemoveFriendID()); + + if (condemn == null) + return true; + + condemn.setActive(msg.isReverseKOS()); + openFriendsCondemnListMsg.setReverseKOS(condemn.isActive()); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + case REVERSEKOS: + + + sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (sourceBuilding == null) + return true; + + if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player)) + return true; + + if (!sourceBuilding.setReverseKOS(msg.isReverseKOS())) + return true; + break; + + //ADD GUILD CONDEMN + case ADDCONDEMN: + + sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (sourceBuilding == null) + return true; + + if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player)) + return true; + + switch (msg.getInviteType()) { + case 2: + + if (msg.getPlayerID() == 0) + return true; + + if (msg.getPlayerType() != GameObjectType.PlayerCharacter.ordinal()) + return true; + + PlayerCharacter playerCharacter = PlayerCharacter.getFromCache(msg.getPlayerID()); + + if (playerCharacter == null) + return true; + + if (Guild.sameNationExcludeErrant(sourceBuilding.getGuild(), playerCharacter.getGuild())) + return true; + + if (sourceBuilding.getCondemned().containsKey(playerCharacter.getObjectUUID())) + return true; + + if (!DbManager.BuildingQueries.ADD_TO_CONDEMNLIST(sourceBuilding.getObjectUUID(), playerCharacter.getObjectUUID(), msg.getGuildID(), msg.getInviteType())) { + Logger.debug("Failed to add Condemned: " + playerCharacter.getFirstName() + " to Building With UID " + sourceBuilding.getObjectUUID()); + return true; + } + + sourceBuilding.getCondemned().put(playerCharacter.getObjectUUID(), new Condemned(playerCharacter.getObjectUUID(), sourceBuilding.getObjectUUID(), msg.getGuildID(), msg.getInviteType())); + break; + case 4: + if (msg.getGuildID() == 0) + return true; + + if (sourceBuilding.getCondemned().containsKey(msg.getGuildID())) + return true; + + Guild condemnedGuild = Guild.getGuild(msg.getGuildID()); + + if (condemnedGuild == null) + return true; + + if (!DbManager.BuildingQueries.ADD_TO_CONDEMNLIST(sourceBuilding.getObjectUUID(), msg.getPlayerID(), condemnedGuild.getObjectUUID(), msg.getInviteType())) { + Logger.debug("Failed to add Condemned: " + condemnedGuild.getName() + " to Building With UID " + sourceBuilding.getObjectUUID()); + return true; + } + + sourceBuilding.getCondemned().put(condemnedGuild.getObjectUUID(), new Condemned(msg.getPlayerID(), sourceBuilding.getObjectUUID(), condemnedGuild.getObjectUUID(), msg.getInviteType())); + break; + case 5: + if (msg.getNationID() == 0) + return true; + + if (sourceBuilding.getCondemned().containsKey(msg.getNationID())) + return true; + + Guild condemnedNation = Guild.getGuild(msg.getNationID()); + + if (condemnedNation == null) + return true; + + if (!DbManager.BuildingQueries.ADD_TO_CONDEMNLIST(sourceBuilding.getObjectUUID(), msg.getPlayerID(), condemnedNation.getObjectUUID(), msg.getInviteType())) { + Logger.debug("Failed to add Condemned: " + condemnedNation.getName() + " to Building With UID " + sourceBuilding.getObjectUUID()); + return true; + } + + sourceBuilding.getCondemned().put(condemnedNation.getObjectUUID(), new Condemned(msg.getPlayerID(), sourceBuilding.getObjectUUID(), condemnedNation.getObjectUUID(), msg.getInviteType())); + break; + + } + + openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(12, sourceBuilding.getCondemned(), sourceBuilding.reverseKOS); + openFriendsCondemnListMsg.configure(); + dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + + //ADD FRIEND BUILDING + case ADDFRIEND: + sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (sourceBuilding == null) + return true; + + if (msg.getGuildID() == 0) + return true; + + if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player)) + return true; + + PlayerCharacter playerCharacter = null; + + + Guild guildInvited = Guild.getGuild(msg.getGuildID()); + + if (guildInvited == null) + return true; + + //Check to see if the invited is already on the friends list. + switch (msg.getInviteType()) { + case 7: + playerCharacter = PlayerCharacter.getFromCache(msg.getPlayerID()); + if (playerCharacter == null) + return true; + if (sourceBuilding.getFriends().containsKey(playerCharacter.getObjectUUID())) + return true; + break; + case 8: + case 9: + if (sourceBuilding.getFriends().containsKey(guildInvited.getObjectUUID())) + return true; + break; + } + + if (!DbManager.BuildingQueries.ADD_TO_FRIENDS_LIST(sourceBuilding.getObjectUUID(), msg.getPlayerID(), guildInvited.getObjectUUID(), msg.getInviteType())) { + Logger.debug("Failed to add Friend: " + playerCharacter.getFirstName() + " to Building With UID " + sourceBuilding.getObjectUUID()); + return true; + } + + switch (msg.getInviteType()) { + case 7: + sourceBuilding.getFriends().put(playerCharacter.getObjectUUID(), new BuildingFriends(playerCharacter.getObjectUUID(), sourceBuilding.getObjectUUID(), playerCharacter.getGuild().getObjectUUID(), 7)); + break; + case 8: + sourceBuilding.getFriends().put(guildInvited.getObjectUUID(), new BuildingFriends(msg.getPlayerID(), sourceBuilding.getObjectUUID(), guildInvited.getObjectUUID(), 8)); + break; + case 9: + sourceBuilding.getFriends().put(guildInvited.getObjectUUID(), new BuildingFriends(msg.getPlayerID(), sourceBuilding.getObjectUUID(), guildInvited.getObjectUUID(), 9)); + break; + } + + openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(26, sourceBuilding.getFriends()); + openFriendsCondemnListMsg.configure(); + + dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + //REMOVE from friends list. + case REMOVEFRIEND: + sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (sourceBuilding == null) + return true; + + if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player)) + return true; + + BuildingFriends friend = sourceBuilding.getFriends().get(msg.getRemoveFriendID()); + + if (friend == null) + return true; + + if (!DbManager.BuildingQueries.REMOVE_FROM_FRIENDS_LIST(sourceBuilding.getObjectUUID(), friend.getPlayerUID(), friend.getGuildUID(), friend.getFriendType())) { + Logger.debug("Failed to remove Friend: " + msg.getRemoveFriendID() + " from Building With UID " + sourceBuilding.getObjectUUID()); + return true; + } + sourceBuilding.getFriends().remove(msg.getRemoveFriendID()); + + openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(26, sourceBuilding.getFriends()); + openFriendsCondemnListMsg.configure(); + dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + break; + //view Friends + case VIEWFRIENDS: + + Building building = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + if (building == null) + return true; + + if (!BuildingManager.PlayerCanControlNotOwner(building, player)) + return true; + + //this message is sent twice back????? + + openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(26, building.getFriends()); + openFriendsCondemnListMsg.configure(); + + + dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + break; + + default: + break; + } + return false; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/OrderNPCMsgHandler.java b/src/engine/net/client/handlers/OrderNPCMsgHandler.java new file mode 100644 index 00000000..d0a9793a --- /dev/null +++ b/src/engine/net/client/handlers/OrderNPCMsgHandler.java @@ -0,0 +1,637 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.Enum.GameObjectType; +import engine.Enum.ProfitType; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.NPCManager; +import engine.gameManager.SessionManager; +import engine.math.FastMath; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +/* + * @Author: + * @Summary: Processes application protocol message which keeps + * client's tcp connection open. + */ +public class OrderNPCMsgHandler extends AbstractClientMsgHandler { + + // Constants used for incoming message type + + private static final int CLIENT_UPGRADE_REQUEST = 3; + private static final int CLIENT_REDEED_REQUEST = 6; + private static final int SVR_CLOSE_WINDOW = 4; + + public OrderNPCMsgHandler() { + super(OrderNPCMsg.class); + } + + public static void processRedeedMob(Mob mob, Building building, ClientConnection origin) { + + PlayerCharacter player; + Contract contract; + CharacterItemManager itemMan; + ItemBase itemBase; + Item item; + + player = SessionManager.getPlayerCharacter(origin); + itemMan = player.getCharItemManager(); + + contract = mob.getContract(); + + if (!player.getCharItemManager().hasRoomInventory((short) 1)) { + ErrorPopupMsg.sendErrorPopup(player, 21); + return; + } + + if (!building.getHirelings().containsKey(mob)) + return; + + if (!NPCManager.removeMobileFromBuilding(mob, building)) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return; + } + + building.getHirelings().remove(mob); + + itemBase = ItemBase.getItemBase(contract.getContractID()); + + if (itemBase == null) { + Logger.error("Could not find Contract for npc: " + mob.getObjectUUID()); + return; + } + + boolean itemWorked = false; + + item = new Item(itemBase, player.getObjectUUID(), Enum.OwnerType.PlayerCharacter, (byte) ((byte) mob.getRank() - 1), (byte) ((byte) mob.getRank() - 1), (short) 1, (short) 1, true, false, Enum.ItemContainerType.INVENTORY, (byte) 0, new ArrayList<>(), ""); + item.setNumOfItems(1); + item.containerType = Enum.ItemContainerType.INVENTORY; + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + itemWorked = true; + } catch (Exception e) { + Logger.error(e); + } + + if (itemWorked) { + itemMan.addItemToInventory(item); + itemMan.updateInventory(); + } + + ManageCityAssetsMsg mca = new ManageCityAssetsMsg(); + mca.actionType = NPC.SVR_CLOSE_WINDOW; + mca.setTargetType(building.getObjectType().ordinal()); + mca.setTargetID(building.getObjectUUID()); + origin.sendMsg(mca); + + } + + private static void modifyBuyProfit(OrderNPCMsg msg, ClientConnection origin) { + NPC npc; + PlayerCharacter player; + Building building; + float percent; + + ProfitType profitType = null; + player = origin.getPlayerCharacter(); + + if (player == null) + return; + + npc = NPC.getFromCache(msg.getNpcUUID()); + + if (npc == null) + return; + + building = npc.getBuilding(); + + if (building == null) + return; + + NPCProfits profit = NPC.GetNPCProfits(npc); + + if (profit == null) + return; + + switch (msg.getActionType()) { + case 10: + profitType = ProfitType.BuyNormal; + break; + case 11: + profitType = ProfitType.BuyGuild; + break; + case 12: + profitType = ProfitType.BuyNation; + } + + percent = msg.getBuySellPercent(); + percent = FastMath.clamp(percent, 0.0f, 1.0f); + + NPCProfits.UpdateProfits(npc, profit, profitType, percent); + } + + private static void modifySellProfit(OrderNPCMsg orderNPCMsg, ClientConnection origin) { + NPC npc; + PlayerCharacter player; + Building building; + float percent; + + ProfitType profitType = null; + + player = origin.getPlayerCharacter(); + + if (player == null) + return; + + npc = NPC.getFromCache(orderNPCMsg.getNpcUUID()); + + if (npc == null) + return; + + building = npc.getBuilding(); + + if (building == null) + return; + + NPCProfits profit = NPC.GetNPCProfits(npc); + + if (profit == null) + return; + + switch (orderNPCMsg.getActionType()) { + case 7: + profitType = ProfitType.SellNormal; + break; + case 8: + profitType = ProfitType.SellGuild; + break; + case 9: + profitType = ProfitType.SellNation; + } + + percent = orderNPCMsg.getBuySellPercent(); + + percent -= 1f; + percent = FastMath.clamp(percent, 0.0f, 3.0f); + + NPCProfits.UpdateProfits(npc, profit, profitType, percent); + } + + private static void handleCityCommand(OrderNPCMsg orderNpcMsg, PlayerCharacter player) { + + Building building = BuildingManager.getBuildingFromCache(orderNpcMsg.getBuildingUUID()); + + if (building == null) + return; + + if (ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building) == false) + return; + + if (orderNpcMsg.getPatrolSize() >= 20) + Logger.info(player.getName() + " is attempting to add patrol points amount " + orderNpcMsg.getPatrolSize()); + + if (orderNpcMsg.getSentrySize() >= 20) + Logger.info(player.getName() + " is attempting to add patrol points amount " + orderNpcMsg.getSentryPoints()); + + if (orderNpcMsg.getPatrolPoints() != null) { + + if (!AddPatrolPoints(building.getObjectUUID(), orderNpcMsg.getPatrolPoints())) { + ErrorPopupMsg.sendErrorMsg(player, "Patrol Points must be placed on city zone."); + return; + } + + for (AbstractCharacter guard : building.getHirelings().keySet()) { + if (guard.getObjectType() == GameObjectType.Mob) + ((Mob) guard).setPatrolPointIndex(0); + } + } else if (building.getPatrolPoints() != null) + ClearPatrolPoints(building.getObjectUUID()); + + if (orderNpcMsg.getSentryPoints() != null) { + AddSentryPoints(building.getObjectUUID(), orderNpcMsg.getSentryPoints()); + } else if (building.getSentryPoints() != null) + ClearSentryPoints(building.getObjectUUID()); + + // Dispatch dispatch = Dispatch.borrow(pc, msg); + // DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + private static void processUpgradeNPC(PlayerCharacter player, AbstractCharacter abstractCharacter) { + + Building building; + + switch (abstractCharacter.getObjectType()) { + + case NPC: + NPC npc = (NPC) abstractCharacter; + building = npc.getBuilding(); + + // Cannot upgrade an npc not within a building + + if (building == null) + return; + + City buildingCity = building.getCity(); + + if (buildingCity == null) { + npc.processUpgradeNPC(player); + return; + } + + buildingCity.transactionLock.writeLock().lock(); + + try { + npc.processUpgradeNPC(player); + } catch (Exception e) { + Logger.error(e); + } finally { + buildingCity.transactionLock.writeLock().unlock(); + } + break; + case Mob: + + Mob mob = (Mob) abstractCharacter; + building = mob.building; + + if (mob.building == null) + return; + + City mobCity = building.getCity(); + + if (mobCity == null) { + mob.processUpgradeMob(player); + return; + } + + mobCity.transactionLock.writeLock().lock(); + + try { + mob.processUpgradeMob(player); + } catch (Exception e) { + // TODO Auto-generated catch block + Logger.error(e); + } finally { + mobCity.transactionLock.writeLock().unlock(); + } + break; + } + } + + private static synchronized void processRedeedNPC(AbstractCharacter abstractCharacter, Building building, ClientConnection origin) { + + // Member variable declaration + + switch (abstractCharacter.getObjectType()) { + case NPC: + NPC npc = (NPC) abstractCharacter; + + Building cityBuilding = npc.getBuilding(); + + if (cityBuilding == null) + return; + + BuildingManager.processRedeedNPC(npc, npc.building, origin); + break; + case Mob: + Mob mob = (Mob) abstractCharacter; + processRedeedMob(mob, mob.building, origin); + break; + } + } + + private static boolean AddPatrolPoints(int buildingID, ArrayList patrolPoints) { + + Building building = BuildingManager.getBuildingFromCache(buildingID); + + if (building == null) + return false; + + Zone zone = building.getParentZone(); + + if (zone == null) + return false; + + if (zone.getPlayerCityUUID() == 0) + return false; + + City city = building.getCity(); + + if (city == null) + return false; + + + //clear first. + + for (Vector3fImmutable point : patrolPoints) { + + if (city.isLocationOnCityZone(point) == false) { + return false; + } + + } + + DbManager.BuildingQueries.CLEAR_PATROL(buildingID); + + for (Vector3fImmutable point : patrolPoints) { + + if (!DbManager.BuildingQueries.ADD_TO_PATROL(buildingID, point)) + return false; + } + building.patrolPoints = patrolPoints; + return true; + } + + private static boolean AddSentryPoints(int buildingID, ArrayList sentryPoints) { + + Building building = BuildingManager.getBuildingFromCache(buildingID); + + if (building == null) + return false; + + building.sentryPoints = sentryPoints; + return true; + } + + private static boolean ClearPatrolPoints(int buildingID) { + + Building building = BuildingManager.getBuildingFromCache(buildingID); + + if (building == null) + return false; + + if (building.patrolPoints == null) + return true; + + if (DbManager.BuildingQueries.CLEAR_PATROL(buildingID) == false) + return false; + + building.patrolPoints.clear(); + return true; + } + + private static boolean ClearSentryPoints(int buildingID) { + + Building building = BuildingManager.getBuildingFromCache(buildingID); + + if (building == null) + return false; + + if (building.sentryPoints == null) + return true; + + building.sentryPoints.clear(); + return true; + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declarations + + PlayerCharacter player; + NPC npc; + Mob mob; + Building building; + OrderNPCMsg orderNPCMsg; + ManageCityAssetsMsg outMsg; + + // Member variable assignment + orderNPCMsg = (OrderNPCMsg) baseMsg; + + if (origin.ordernpcspam > System.currentTimeMillis()) + return true; + + origin.ordernpcspam = System.currentTimeMillis() + 500; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + if (orderNPCMsg.getActionType() == 28) { + OrderNPCMsgHandler.handleCityCommand(orderNPCMsg, player); + return true; + } + + if (orderNPCMsg.getObjectType() == GameObjectType.NPC.ordinal()) { + + npc = NPC.getFromCache(orderNPCMsg.getNpcUUID()); + + if (npc == null) + return true; + + building = BuildingManager.getBuildingFromCache(orderNPCMsg.getBuildingUUID()); + + if (building == null) + return true; + + if (building.getHirelings().containsKey(npc) == false) + return true; + + + if (player.getCharItemManager().getTradingWith() != null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot barter and trade with same timings."); + return true; + } + + player.lastBuildingAccessed = building.getObjectUUID(); + + switch (orderNPCMsg.getActionType()) { + + case 2: + player = SessionManager.getPlayerCharacter(origin); + + if (ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building) == false) + return true; + + if (building.getHirelings().containsKey(npc) == false) + return true; + + if (npc.remove() == false) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return true; + } + + ManageCityAssetsMsg manageCityAssetsMsg = new ManageCityAssetsMsg(); + manageCityAssetsMsg.actionType = SVR_CLOSE_WINDOW; + manageCityAssetsMsg.setTargetType(building.getObjectType().ordinal()); + manageCityAssetsMsg.setTargetID(building.getObjectUUID()); + + Dispatch dispatch = Dispatch.borrow(player, manageCityAssetsMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + return true; + + case CLIENT_UPGRADE_REQUEST: + + if (BuildingManager.playerCanManage(player, building) == false) + return true; + + processUpgradeNPC(player, npc); + + outMsg = new ManageCityAssetsMsg(player, building); + + // Action TYPE + outMsg.actionType = 3; + outMsg.setTargetType(building.getObjectType().ordinal()); + outMsg.setTargetID(building.getObjectUUID()); + outMsg.setTargetType3(building.getObjectType().ordinal()); + outMsg.setTargetID3(building.getObjectUUID()); + outMsg.setAssetName1(building.getName()); + outMsg.setUnknown54(1); + + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + break; + case CLIENT_REDEED_REQUEST: + + if (BuildingManager.PlayerCanControlNotOwner(building, player) == false) + return true; + + processRedeedNPC(npc, building, origin); + return true; + //MB TODO HANDLE all profits. + case 7: + case 8: + case 9: + + if (BuildingManager.PlayerCanControlNotOwner(building, player) == false) + return true; + + modifySellProfit(orderNPCMsg, origin); + dispatch = Dispatch.borrow(player, orderNPCMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 10: + case 11: + case 12: + + if (BuildingManager.PlayerCanControlNotOwner(building, player) == false) + return true; + + modifyBuyProfit(orderNPCMsg, origin); + dispatch = Dispatch.borrow(player, orderNPCMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + + // Validation check Owner or IC or friends + if (BuildingManager.PlayerCanControlNotOwner(building, player) == false) + if (BuildingManager.playerCanManage(player, building) == false) + return true; + + ManageNPCMsg manageNPCMsg = new ManageNPCMsg(npc); + Dispatch dispatch = Dispatch.borrow(player, manageNPCMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + + } else if (orderNPCMsg.getObjectType() == GameObjectType.Mob.ordinal()) { + + mob = Mob.getFromCacheDBID(orderNPCMsg.getNpcUUID()); + + if (mob == null) + return true; + + building = BuildingManager.getBuildingFromCache(orderNPCMsg.getBuildingUUID()); + + if (building == null) + return true; + + if (!building.getHirelings().containsKey(mob)) + return true; + + if (player.getCharItemManager().getTradingWith() != null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot barter and trade with same timings."); + return true; + } + + player.lastBuildingAccessed = building.getObjectUUID(); + + switch (orderNPCMsg.getActionType()) { + case 2: + + if (BuildingManager.playerCanManage(player, building) == false) + return true; + + if (building.getHirelings().containsKey(mob) == false) + return true; + + if (NPCManager.removeMobileFromBuilding(mob, building) == false) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return true; + } + + ManageCityAssetsMsg manageCityAssetsMsg = new ManageCityAssetsMsg(); + manageCityAssetsMsg.actionType = SVR_CLOSE_WINDOW; + manageCityAssetsMsg.setTargetType(building.getObjectType().ordinal()); + manageCityAssetsMsg.setTargetID(building.getObjectUUID()); + Dispatch dispatch = Dispatch.borrow(player, manageCityAssetsMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + break; + case 3: + + if (BuildingManager.PlayerCanControlNotOwner(building, player) == false) + return true; + + processUpgradeNPC(player, mob); + + outMsg = new ManageCityAssetsMsg(player, building); + + // Action TYPE + outMsg.actionType = 3; + outMsg.setTargetType(building.getObjectType().ordinal()); + outMsg.setTargetID(building.getObjectUUID()); + outMsg.setTargetType3(building.getObjectType().ordinal()); + outMsg.setTargetID3(building.getObjectUUID()); + outMsg.setAssetName1(building.getName()); + outMsg.setUnknown54(1); + + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + break; + case 6: + + if (BuildingManager.PlayerCanControlNotOwner(building, player) == false) + return true; + + processRedeedNPC(mob, building, origin); + return true; + //MB TODO HANDLE all profits. + case 7: + case 8: + case 9: + break; + case 10: + case 11: + case 12: + break; + } + + // Validation check Owner or IC + if (BuildingManager.PlayerCanControlNotOwner(building, player) == false) + if (BuildingManager.playerCanManage(player, building) == false) + return true; + + ManageNPCMsg manageNPCMsg = new ManageNPCMsg(mob); + Dispatch dispatch = Dispatch.borrow(player, manageNPCMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/PetitionReceivedMsgHandler.java b/src/engine/net/client/handlers/PetitionReceivedMsgHandler.java new file mode 100644 index 00000000..e53bcc8d --- /dev/null +++ b/src/engine/net/client/handlers/PetitionReceivedMsgHandler.java @@ -0,0 +1,60 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.DbManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.PetitionReceivedMsg; +import engine.objects.Petition; +import engine.objects.PlayerCharacter; + +public class PetitionReceivedMsgHandler extends AbstractClientMsgHandler { + + public static final int PETITION_NEW = 1; + public static final int PETITION_CANCEL = 2; + public static final int PETITION_CLOSE = 4; + + public PetitionReceivedMsgHandler() { + super(PetitionReceivedMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg msg, ClientConnection origin) throws MsgSendException { + + if (msg == null) + return true; + + PetitionReceivedMsg petitionReceivedMsg = (PetitionReceivedMsg) msg; + + if (origin == null) + return true; + + PlayerCharacter playerCharacter = origin.getPlayerCharacter(); + + if (playerCharacter == null) + return true; + + Petition petition = new Petition(petitionReceivedMsg, origin); + + // Write petition to database + + if (petitionReceivedMsg.petition == PETITION_NEW) + DbManager.PetitionQueries.WRITE_PETITION_TO_TABLE(petition); + + // Close the petition window + + if (petitionReceivedMsg.petition == PETITION_NEW) + petitionReceivedMsg.petition = PETITION_CLOSE; + + petitionReceivedMsg.unknownByte01 = 0; + petitionReceivedMsg.unknown04 = 0; + + Dispatch dispatch = Dispatch.borrow(playerCharacter, petitionReceivedMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + } +} diff --git a/src/engine/net/client/handlers/PlaceAssetMsgHandler.java b/src/engine/net/client/handlers/PlaceAssetMsgHandler.java new file mode 100644 index 00000000..382b6547 --- /dev/null +++ b/src/engine/net/client/handlers/PlaceAssetMsgHandler.java @@ -0,0 +1,1381 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.*; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.InterestManager; +import engine.InterestManagement.RealmMap; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.CityRecord; +import engine.db.archive.DataWarehouse; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.math.Bounds; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.CityZoneMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.PlaceAssetMsg; +import engine.net.client.msg.PlaceAssetMsg.PlacementInfo; +import engine.objects.*; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/* + * @Summary: Processes application protocol message which requests + * creation of new city / buildings from seeds/deeds in inventory. + */ +public class PlaceAssetMsgHandler extends AbstractClientMsgHandler { + + // Useful constants + // ActionType 1 = client request + // 2 = Server confirms open window + // 3 = Request to place asset + // 4 = Server confirms/close window + private static final int CLIENTREQ_UNKNOWN = 1; + private static final int SERVER_OPENWINDOW = 2; + private static final int CLIENTREQ_NEWBUILDING = 3; // Request to place asset + private static final int SERVER_CLOSEWINDOW = 4; + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + public PlaceAssetMsgHandler() { + + super(PlaceAssetMsg.class); + + } + + private static void closePlaceAssetWindow(ClientConnection origin) { + + // Action type 4 is the server telling the client to + // close the asset placement window. + // This is believed to be a confirmation message to the client + PlaceAssetMsg pam = new PlaceAssetMsg(); + pam.setActionType(4); + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), pam); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + // Default method: Validates and places all buildings that do not + // require special treatment in some fashion. + + private static boolean validateTreeOfLifePlacement(PlayerCharacter playerCharacter, Realm serverRealm, Zone serverZone, + ClientConnection origin, PlaceAssetMsg msg) { + + PlacementInfo placementInfo = msg.getFirstPlacementInfo(); + + // Your guild already owns a tree + + if (playerCharacter.getGuild().getOwnedCity() != null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Your guild already owns a tree!"); + return false; + } + + // Validate that the player is the leader of a guild + + if (GuildStatusController.isGuildLeader(playerCharacter.getGuildStatus()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 10, ""); // Must be a guild leader + return false; + } + + // Validate that the player is the leader of a guild + // that is not currently Sovereign *** BUG? Doesn't look right. isGuildLeader()? + + if ((playerCharacter.getGuild().getGuildState() != GuildState.Sworn + || playerCharacter.getGuild().getGuildState() != GuildState.Errant) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 17, ""); // Your is not an errant or soverign guild + return false; + } + + // All trees must be placed within a continent. + + if (!serverZone.isContinent()) { + + PlaceAssetMsg.sendPlaceAssetError(origin, 69, ""); // Tree must be within a territory + return false; + } + + if (serverRealm == null || serverRealm.getCanPlaceCities() == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 57, playerCharacter.getName()); // No building may be placed within this territory + return false; + } + + // Cannot place a tree underwater + + if (HeightMap.isLocUnderwater(placementInfo.getLoc())) { + PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater + return false; + } + + //Test city not too close to any other zone + + if (!ZoneManager.validTreePlacementLoc(serverZone, placementInfo.getLoc().x, placementInfo.getLoc().z)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 39, ""); // Too close to another tree + return false; + } + + // Validate that Realm is not at it's city limit + + if (serverRealm.isRealmFull() == true) { + int numCities; + numCities = serverRealm.getNumCities(); + PlaceAssetMsg.sendPlaceAssetError(origin, 58, Integer.toString(numCities)); // This territory is full + return false; + } + + return true; + } + + private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) { + + if (serverZone.isPlayerCity() == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName()); + return false; + } + + City city = ZoneManager.getCityAtLocation(placementInfo.getLoc()); + + if (player.getGuild().equals(city.getGuild()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 40, player.getName()); + return false; + } + + if (city.isLocationOnCityGrid(placementInfo.getLoc()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 41, player.getName()); + return false; + } + + // Retrieve the building details we're placing + + if (serverZone.isNPCCity() == true) { + PlaceAssetMsg.sendPlaceAssetError(origin, 15, ""); // Cannot place in a peace zone + return false; + } + + // Errant guilds cannot place assets + + if (player.getGuild().getGuildState() == GuildState.Errant) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Only sovereign or sworn guilds may place assets."); + return false; + } + + // Player must be GL or IC of a guild to place buildings. + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 10, ""); // You must be a guild leader + return false; + } + + // Cannot place a building underwater + + if (HeightMap.isLocUnderwater(placementInfo.getLoc())) { + PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater + return false; + } + + // Players cannot place buildings in mob zones. + + if ((serverZone.isMacroZone() == true) + || (serverZone.getParent().isMacroZone() == true)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 57, player.getName()); // No building may be placed within this territory + return false; + } + + Realm serverRealm = RealmMap.getRealmAtLocation(city.getLoc()); + + // Cannot place buildings on seafloor or other restricted realms + + if (serverRealm == null || serverRealm.getCanPlaceCities() == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 57, player.getName()); // No building may be placed within this territory + return false; + } + + // Cannot place assets on a dead tree + + if ((serverZone.isPlayerCity()) + && (City.getCity(serverZone.getPlayerCityUUID()).getTOL().getRank() == -1)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot place asset on dead tree until world heals"); + return false; + } + + if (placementCollisionCheck(serverZone, origin, placementInfo)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets + return false; + } + + return true; + } + + private static boolean placementCollisionCheck(Zone serverZone, ClientConnection origin, PlacementInfo placementInfo) { + // Overlap check + + for (Building building : serverZone.zoneBuildingSet) { + + if ((building.getBlueprintUUID() != 0) && (Bounds.collide(placementInfo, building) == true)) { + + // Ignore and remove from simulation if we are placing over rubble + + if (building.getRank() == -1) { + + if ((building.getBlueprintUUID() != 0) + && (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) { + Shrine.RemoveShrineFromCacheByBuilding(building); + if (building.getCity() != null) { + + } + } + + building.removeFromCache(); + WorldGrid.RemoveWorldObject(building); + WorldGrid.removeObject(building); + building.getParentZone().zoneBuildingSet.remove(building); + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) { + building.RemoveFromBarracksList(); + } + continue; + } + + + PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets + return true; + } + } + return false; + } + + private static boolean validateCityBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo buildingInfo) { + + // Perform shared common validation first + + if (validateBuildingPlacement(serverZone, msg, origin, player, buildingInfo) == false) + return false; + + // Must be a player city + + if (serverZone.isPlayerCity() == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 41, player.getName()); // Cannot place outside a guild zone + return false; + } + + //Test zone has a city object + + City city = City.getCity(serverZone.getPlayerCityUUID()); + + if (city == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 52, ""); //"no city to associate asset with" + return false; + } + + // City assets must be placed on the city grid + + if (!city.isLocationOnCityGrid(buildingInfo.getLoc())) { + PlaceAssetMsg.sendPlaceAssetError(origin, 52, ""); + return false; + } + + // Make sure it's not an errant tree + + if ((city.getGuild() == null || city.getGuild().isEmptyGuild() == true)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 18, ""); //"There are no guild trees to be found" + return false; + } + + //Test player is in correct guild to place buildings + + if (!player.isCSR) + if (player.getGuild().getObjectUUID() != city.getGuild().getObjectUUID()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 9, ""); //You must be a guild member to place this asset + return false; + } + return true; + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlaceAssetMsg msg; + Boolean buildingCreated; + + // Character location and session + + PlayerCharacter playerCharacter; + PlacementInfo buildingList; + Blueprint buildingBlueprint; + + // Tell compiler it's ok to trust us and parse + // what we need from the message structure + + msg = (PlaceAssetMsg) baseMsg; + + // Action type 3 is a client requesting to place an object + // For all other action types let's just early exit + + if (msg.getActionType() != CLIENTREQ_NEWBUILDING) + return true; + + // assign our character + + playerCharacter = SessionManager.getPlayerCharacter(origin); + + // We need to figure out what exactly the player is attempting + // to place, as some objects like tol/bane/walls are edge cases. + // So let's get the first item in their list. + + buildingList = msg.getFirstPlacementInfo(); + + // Early exit if null building list. + + if (buildingList == null) { + Logger.error("Player " + playerCharacter.getCombinedName() + + " null building list on deed use"); + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + closePlaceAssetWindow(origin); + return true; + } + + Item contract = null; + + for (Item inventoryItem : playerCharacter.getInventory()) { + if (inventoryItem.getItemBase().getUseID() == buildingList.getBlueprintUUID()) { + contract = inventoryItem; + break; + } + } + + // Grab the blueprint from the uuid in the message + + buildingBlueprint = Blueprint.getBlueprint(buildingList.getBlueprintUUID()); + + // Early exit if blueprint can't be retrieved for the object. + + if (buildingBlueprint == null) { + Logger.error("Player " + playerCharacter.getCombinedName() + + " null blueprint UUID: " + buildingList.getBlueprintUUID() + " on deed use"); + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + closePlaceAssetWindow(origin); + return true; + } + + // Let's now attempt to place the building + buildingCreated = false; + + // Many buildings have particular validation and + // post-creation cleanup requirements. + + boolean close = true; + lock.writeLock().lock(); + boolean isSiege = false; + try { + switch (buildingBlueprint.getBuildingGroup()) { + + case TOL: + if (contract == null) + break; + buildingCreated = placeTreeOfLife(playerCharacter, origin, msg); + break; + case WAREHOUSE: + if (contract == null) + break; + if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID())) + break; + buildingCreated = placeWarehouse(playerCharacter, origin, msg); + break; + case SIEGETENT: + case BULWARK: + if (contract == null) + break; + if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID())) + break; + buildingCreated = placeSiegeEquip(playerCharacter, origin, msg); + break; + case SPIRE: + if (contract == null) + break; + if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID())) + break; + buildingCreated = placeSpire(playerCharacter, origin, msg); + break; + case SHRINE: + if (contract == null) + break; + if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID())) + break; + buildingCreated = placeShrine(playerCharacter, origin, msg); + break; + case BARRACK: + if (contract == null) + break; + if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID())) + break; + buildingCreated = placeBarrack(playerCharacter, origin, msg); + break; + case WALLSTRAIGHT: + case WALLCORNER: + case SMALLGATE: + case ARTYTOWER: + case WALLSTAIRS: + case WALLSTRAIGHTTOWER: + buildingCreated = placeCityWalls(playerCharacter, origin, msg); + close = false; + break; + default: + if (contract == null) + break; + if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID())) + break; + buildingCreated = placeSingleBuilding(playerCharacter, origin, msg); + break; + } + } catch (Exception e) { + Logger.error("PlaceAssetHandler", e.getMessage()); + e.printStackTrace(); + } finally { + lock.writeLock().unlock(); + } + + // Remove the appropriate deed. + if (buildingCreated == true) + if (contract != null) { + playerCharacter.getCharItemManager().delete(contract); + playerCharacter.getCharItemManager().updateInventory(); + } + + // Close the window. We're done! + //DONT CLOSE THE WINDOW IF WALL KTHANX + + if (close) + closePlaceAssetWindow(origin); + return true; + } + + private boolean placeSingleBuilding(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) { + + PlacementInfo buildingList; + Zone serverZone; + + // Retrieve the building details we're placing + + buildingList = msg.getFirstPlacementInfo(); + + serverZone = ZoneManager.findSmallestZone(buildingList.getLoc()); + // Early exit if something went horribly wrong + // with locating the current or zone + + if (serverZone == null) { + Logger.error("Null zone in placeSingleBuilding"); + return false; + } + + // Method checks validation conditions arising when placing + // buildings. Player must be on a city grid, must be + // inner council of the city's guild, etc. + + if (validateBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false) + return false; // Close window here? + + // Place the building + if (createStructure(playerCharacter, buildingList, serverZone) == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + return true; + } + + private boolean placeWarehouse(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) { + + Zone serverZone; + City cityObject; + PlacementInfo buildingList; + + // Retrieve the building details we're placing + + buildingList = msg.getFirstPlacementInfo(); + + // Setup working variables we'll need + + serverZone = ZoneManager.findSmallestZone(buildingList.getLoc()); + + // Early exit if something went horribly wrong + + if (serverZone == null) + return false; + + cityObject = City.getCity(serverZone.getPlayerCityUUID()); + + // Early exit if something went horribly wrong + + if (cityObject == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 52, ""); + return false; + } + + // Method checks validation conditions arising when placing + // buildings. Player must be on a city grid, must be + // inner council of the city's guild, etc. + + if (validateCityBuildingPlacement(serverZone, msg, origin, player, buildingList) == false) + return false; + + if (cityObject.getWarehouse() != null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 50, ""); //"You can only have one warehouse" + return false; + } + + // Create the warehouse object and it's entry in the database + + if (createWarehouse(player, msg.getFirstPlacementInfo(), serverZone) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + return true; + } + + private boolean placeSiegeEquip(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) { + + Zone serverZone; + Building siegeBuilding; + PlacementInfo buildingList; + City serverCity; + Bane bane; + + // Retrieve the building details we're placing + + buildingList = msg.getFirstPlacementInfo(); + + // Setup working variables we'll need + + serverZone = ZoneManager.findSmallestZone(buildingList.getLoc()); + + // Early exit if something went horribly wrong + // with locating the current city and/or zone + + if (serverZone == null) { + Logger.error("Error obtaining reference to zone"); + return false; + } + + serverCity = ZoneManager.getCityAtLocation(buildingList.getLoc()); + + // No valid player city found + + if (serverCity == null || serverCity.getParent().isPlayerCity() == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 52, ""); // Cannot place outisde a guild zone + return false; + } + + // No bane no bow + + bane = serverCity.getBane(); + + if (bane == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 66, ""); // There is no bane circle to support this building of war + return false; + } + + // Must belong to either attacker or defenders. + + if ((player.getGuild().equals(serverCity.getBane().getOwner().getGuild()) == false) + && (player.getGuild().equals(serverCity.getGuild()) == false)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 54, ""); // Must belong to attacker or defender + return false; + } + + // Player must be GL or IC of the bane guild to place bow. + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false + && GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 10, player.getName()); // You must be a guild leader to place this asset + return false; + } + + // Attackers cannot place on grid until bane is live + + if (bane.getSiegePhase() != SiegePhase.WAR && + player.getGuild().equals(serverCity.getBane().getOwner().getGuild()) && + serverCity.isLocationOnCityGrid(buildingList.getLoc())) { + PlaceAssetMsg.sendPlaceAssetError(origin, 53, player.getName()); // Buildings of war cannot be placed around a city grid unless there is an active bane + return false; + } + + // If there is a bane placed, we limit bow placement to 2x the stone rank's worth of attacker assets + // and 1x the tree rank for defenders + + if (validateSiegeLimits(player, origin, serverCity.getBane()) == false) + return false; + + // Collision check (Removes rubble side effect) + + if (placementCollisionCheck(serverZone, origin, buildingList)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets + return false; + } + + // Create the siege Building + + siegeBuilding = createStructure(player, msg.getFirstPlacementInfo(), serverZone); + + // Oops something went really wrong + + if (siegeBuilding == null) + return false; + + // passes validation: can assign auto-protection to war asset + + siegeBuilding.setProtectionState(ProtectionState.PROTECTED); + + return true; + } + + private boolean validateSiegeLimits(PlayerCharacter playerCharacter, ClientConnection origin, Bane bane) { + + City serverCity = bane.getCity(); + HashSet awoList; + HashSet attackerBuildings = new HashSet<>(); + HashSet defenderBuildings = new HashSet<>(); + ; + int maxAttackerAssets = serverCity.getBane().getStone().getRank() * 2; + int maxDefenderAssets = serverCity.getRank(); + + // Count bow for attackers and defenders + + awoList = WorldGrid.getObjectsInRangePartial(serverCity, 1000, MBServerStatics.MASK_BUILDING); + + for (AbstractWorldObject awo : awoList) { + Building building = (Building) awo; + + if (building.getBlueprint() != null) + if (!building.getBlueprint().isSiegeEquip()) + continue; + + if (!building.getLoc().isInsideCircle(serverCity.getLoc(), CityBoundsType.ZONE.extents)) + continue; + + if (building.getGuild() == null) + continue; + + if (building.getGuild().isEmptyGuild()) + continue; + + if (!building.getGuild().equals(serverCity.getGuild()) && !building.getGuild().equals(serverCity.getBane().getOwner().getGuild())) + continue; + if (building.getRank() < 0) { + continue; + } + if (building.getGuild().equals(serverCity.getGuild())) + defenderBuildings.add(building); + + if (building.getGuild().equals(serverCity.getBane().getOwner().getGuild())) + attackerBuildings.add(building); + + } + // Validate bane limits on siege assets + + if (playerCharacter.getGuild().equals(serverCity.getGuild())) { + //defender attempting to place asset + if (defenderBuildings.size() >= maxDefenderAssets) { + PlaceAssetMsg.sendPlaceAssetError(origin, 62, ""); + return false; + } + } + + if (playerCharacter.getGuild().equals(serverCity.getBane().getStone().getGuild())) { + //attacker attempting to place asset + if (attackerBuildings.size() >= maxAttackerAssets) { + PlaceAssetMsg.sendPlaceAssetError(origin, 61, ""); + return false; + } + } + + // Passed validation + + return true; + } + + private boolean placeTreeOfLife(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) { + + Realm serverRealm; + Zone serverZone; + ArrayList cityObjects; // MySql result set + HashMap cityObjectMap = new HashMap<>(); + PlacementInfo treeInfo; + Guild playerNation; + PlacementInfo treePlacement = msg.getFirstPlacementInfo(); + Building treeObject; + City cityObject; + Zone zoneObject; + + // Setup working variables we'll need + + serverRealm = RealmMap.getRealmAtLocation(treePlacement.getLoc()); + serverZone = ZoneManager.findSmallestZone(treePlacement.getLoc()); + + // Early exit if something went horribly wrong + // with locating the current realm and/or zone + + if (serverRealm == null || serverZone == null) + return false; + + // Method checks validation conditions arising when placing + // trees + + if (validateTreeOfLifePlacement(playerCharacter, serverRealm, serverZone, origin, msg) == false) + return false; + + // Retrieve tree info for the w value it's passing. + + treeInfo = msg.getFirstPlacementInfo(); + + if (treeInfo == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + Vector3fImmutable plantLoc = new Vector3fImmutable(treeInfo.getLoc().x, + HeightMap.getWorldHeight(treeInfo.getLoc()), + treeInfo.getLoc().z); + + cityObjects = DbManager.CityQueries.CREATE_CITY(playerCharacter.getObjectUUID(), serverZone.getObjectUUID(), + serverRealm.getRealmID(), + plantLoc.x - serverZone.getAbsX(), plantLoc.y, + plantLoc.z - serverZone.getAbsZ(), treeInfo.getRot().y, treeInfo.getW(), playerCharacter.getGuild().getName(), LocalDateTime.now()); + + // Uh oh! + if (cityObjects == null || cityObjects.isEmpty()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + // Assign our worker variables after figuring out what + // is what in the result set. + + for (AbstractGameObject gameObject : cityObjects) + cityObjectMap.put(gameObject.getObjectType(), gameObject); + + treeObject = (Building) cityObjectMap.get(GameObjectType.Building); + treeObject.runAfterLoad(); + ; + cityObject = (City) cityObjectMap.get(GameObjectType.City); + zoneObject = (Zone) cityObjectMap.get(GameObjectType.Zone); + + // not allowed to plant a tree if ur not an errant guild. + // Desub from any previous nation. + // This should be done automatically in a method inside Guild *** Refactor + // Player is now a Sovereign guild, configure them as such. + + playerCharacter.getGuild().setNation(playerCharacter.getGuild()); + playerNation = playerCharacter.getGuild(); + playerNation.setGuildState(GuildState.Sovereign); + + // Link the zone with the city and then add + // to the appropriate hash tables and cache + + zoneObject.setPlayerCity(true); + + if (zoneObject.getParent() != null) + zoneObject.getParent().addNode(zoneObject); //add as child to parent + + ZoneManager.addZone(zoneObject.getObjectUUID(), zoneObject); + ZoneManager.addPlayerCityZone(zoneObject); + serverZone.addNode(zoneObject); + + zoneObject.generateWorldAltitude(); + + cityObject.setParent(zoneObject); + cityObject.setObjectTypeMask(MBServerStatics.MASK_CITY); // *** Refactor : should have it already + //Link the tree of life with the new zone + + treeObject.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + treeObject.setParentZone(zoneObject); + MaintenanceManager.setMaintDateTime(treeObject, LocalDateTime.now().plusDays(7)); + + // Update guild binds and tags + //load the new city on the clients + + CityZoneMsg czm = new CityZoneMsg(1, treeObject.getLoc().x, treeObject.getLoc().y, treeObject.getLoc().z, cityObject.getCityName(), zoneObject, Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents); + DispatchMessage.dispatchMsgToAll(czm); + + GuildManager.updateAllGuildBinds(playerNation, cityObject); + GuildManager.updateAllGuildTags(playerNation); + + // Send all the cities to the clients? + // *** Refactor : figure out how to send like, one? + + City.lastCityUpdate = System.currentTimeMillis(); + treeObject.setLoc(treeObject.getLoc()); + InterestManager.setObjectDirty(treeObject); +// WorldGrid.addObject(treeObject, playerCharacter); + + serverRealm.addCity(cityObject.getObjectUUID()); + playerNation.setCityUUID(cityObject.getObjectUUID()); + + // Bypass warehouse entry if we're an admin + + if (playerCharacter.getAccount().status.equals(AccountStatus.ADMIN)) + return true; + + // Push this event to the data warehouse + + CityRecord cityRecord = CityRecord.borrow(cityObject, RecordEventType.CREATE); + DataWarehouse.pushToWarehouse(cityRecord); + + return true; + } + + // Method validates the location we have selected for our new city + + private boolean placeSpire(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) { + + Zone serverZone; + Building spireBuilding; + Blueprint blueprint; + City cityObject; + PlacementInfo buildingList; + + // Setup working variables we'll need + + buildingList = msg.getFirstPlacementInfo(); + + serverZone = ZoneManager.findSmallestZone(buildingList.getLoc()); + + // Early exit if something went horribly wrong + // with locating the current realm and/or city + + if (serverZone == null) + return false; + + cityObject = City.getCity(serverZone.getPlayerCityUUID()); + + if (cityObject == null) + return false; + + // Method checks validation conditions arising when placing + // buildings. Player must be on a city grid, must be + // inner council of the city's guild, etc. + + if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false) + return false; + + // Loop through all buildings in this city looking for a spire of the. + // same type we are placing. There can be only one of each type + + int spireCount = 0; + + blueprint = Blueprint.getBlueprint(msg.getFirstPlacementInfo().getBlueprintUUID()); + + for (Building building : serverZone.zoneBuildingSet) { + + if (building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) { + + if (building.getBlueprintUUID() == blueprint.getMeshForRank(0)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 46, ""); // "Spire of that type exists" + return false; + } + spireCount++; + } + } + + // Too many spires for this tree's rank? + + if (spireCount >= Blueprint.getMaxShrines(cityObject.getTOL().getRank())) { + PlaceAssetMsg.sendPlaceAssetError(origin, 45, ""); //Tree cannot support anymore spires + return false; + } + + // Create the spire + + spireBuilding = createStructure(playerCharacter, msg.getFirstPlacementInfo(), serverZone); + return spireBuilding != null; + } + + private boolean placeShrine(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) { + + Zone serverZone; + Blueprint blueprint; + City cityObject; + PlacementInfo buildingList; + + // Setup working variables we'll need + buildingList = msg.getFirstPlacementInfo(); + + serverZone = ZoneManager.findSmallestZone(buildingList.getLoc()); + + // Early exit if something went horribly wrong + // with locating the current realm and/or zone + if (serverZone == null) + return false; + + // Method checks validation conditions arising when placing + // buildings. Player must be on a city grid, must be + // inner council of the city's guild, etc. + + if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false) + return false; + + // Loop through all buildings in this city looking for a shrine of the. + // same type we are placing. There can be only one of each type + + int shrineCount = 0; + + cityObject = City.getCity(serverZone.getPlayerCityUUID()); + + // Cannot place shrine in abandoned city. Shrines must be owned + // by the tol owner not the person placing them. + + if (cityObject.getTOL().getOwnerUUID() == 0) { + PlaceAssetMsg.sendPlaceAssetError(origin, 42, ""); //Tree cannot support anymore shrines + return false; + } + + blueprint = Blueprint.getBlueprint(msg.getFirstPlacementInfo().getBlueprintUUID()); + + if (blueprint == null) { + return false; + } + + for (Building building : serverZone.zoneBuildingSet) { + if (building.getBlueprint() == null) + continue; + + if (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) { + if (building.getBlueprintUUID() == blueprint.getMeshForRank(0)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 43, ""); // "shrine of that type exists" + return false; + } + shrineCount++; + } + } + + // Too many shrines for this tree's rank? + + if (shrineCount >= Blueprint.getMaxShrines(cityObject.getTOL().getRank())) { + PlaceAssetMsg.sendPlaceAssetError(origin, 42, ""); //Tree cannot support anymore shrines + return false; + } + + // Create the shrine + + return createShrine((PlayerCharacter) cityObject.getTOL().getOwner(), msg.getFirstPlacementInfo(), serverZone); + } + + private boolean placeBarrack(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) { + + Zone serverZone; + City cityObject; + PlacementInfo buildingList; + + // Setup working variables we'll need + buildingList = msg.getFirstPlacementInfo(); + + serverZone = ZoneManager.findSmallestZone(buildingList.getLoc()); + + // Early exit if something went horribly wrong + // with locating the current realm and/or zone + + if (serverZone == null) + return false; + + // Method checks validation conditions arising when placing + // buildings. Player must be on a city grid, must be + // inner council of the city's guild, etc. + + if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false) + return false; + + // Loop through all buildings in this city counting barracks . + + int barracksCount = 0; + + cityObject = City.getCity(serverZone.getPlayerCityUUID()); + + // Cannot place barracks in abandoned city. + + if (cityObject.getTOL().getOwnerUUID() == 0) { + PlaceAssetMsg.sendPlaceAssetError(origin, 42, ""); //Tree cannot support anymore shrines + return false; + } + + for (Building building : serverZone.zoneBuildingSet) { + if (building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) + barracksCount++; + } + + // Too many shrines for this tree's rank? + + if (barracksCount >= cityObject.getTOL().getRank()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 47, ""); //Tree cannot support anymore shrines + return false; + } + + // Create the shrine + + return createBarracks((PlayerCharacter) cityObject.getTOL().getOwner(), msg.getFirstPlacementInfo(), serverZone); + } + + private boolean placeCityWalls(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) { + + // Member variables + + Zone serverZone; + City cityObject; + int placementCost = 0; + CharacterItemManager itemMan; + Item goldItem; + Building wallPiece; + + // Setup working variables we'll need + + serverZone = ZoneManager.findSmallestZone(player.getLoc()); + + // Early exit if something went horribly wrong + + if (serverZone == null) + return false; + + + if (player.getCharItemManager().getGoldTrading() > 0) { + ErrorPopupMsg.sendErrorPopup(player, 195); + return false; + } + + + // Method checks validation conditions arising when placing + // buildings. Player must be on a city grid, must be + // inner council of the city's guild, etc. + + if (validateCityBuildingPlacement(serverZone, msg, origin, player, msg.getFirstPlacementInfo()) == false) + return false; + + cityObject = City.getCity(serverZone.getPlayerCityUUID()); + + // We need to be able to access how much gold a character is carrying + + itemMan = player.getCharItemManager(); + + if (itemMan == null) + + return false; + + goldItem = itemMan.getGoldInventory(); + + // Grab list of walls we're placing + + ArrayList walls = msg.getPlacementInfo(); + + // Character must be able to afford walls + + for (PlacementInfo wall : walls) { + placementCost += PlaceAssetMsg.getWallCost(wall.getBlueprintUUID()); + } + + // Early exit if not enough gold in character's inventory to place walls + + if (placementCost > goldItem.getNumOfItems()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 28, ""); // Not enough gold + return false; + } + + placementCost = 0; // reset placement cost for fix bug with wall pieces somethings not taking gold out if forced an error. + + + // Overlap check and wall deed verifications + for (PlacementInfo wall : walls) { + + if (Blueprint.isMeshWallPiece(wall.getBlueprintUUID()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 48, ""); //"Assets (except walls) must be placed one at a time" + continue; + } + + // Ignore wall pieces not on the city grid + if (cityObject.isLocationOnCityGrid(wall.getLoc()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Asset " + cityObject.getName() + " not on citygrid"); + continue; + } + + // Does this wall collide with any other building? + + for (Building building : serverZone.zoneBuildingSet) { + + + //TODO Clean up collision with placementInfo. don't need to create the same placementinfo bounds for collision checks on each building. + if ((building.getBlueprintUUID() != 0) && (Bounds.collide(wall, building) == true)) { + + if (building.getRank() == -1) { + building.removeFromCache(); + WorldGrid.RemoveWorldObject(building); + WorldGrid.removeObject(building); + building.getParentZone().getParent().zoneBuildingSet.remove(building); + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) { + building.RemoveFromBarracksList(); + } + continue; + } + // remove gold from walls already placed before returning. + + PlaceAssetMsg.sendPlaceAssetError(origin, 3, building.getName()); //"Conflict between assets" + return false; + } + } + placementCost = PlaceAssetMsg.getWallCost(wall.getBlueprintUUID()); + + if (!itemMan.modifyInventoryGold(-placementCost)) { + ChatManager.chatSystemInfo(player, player.getFirstName() + " can't has free moneys! no for real.. Thor.. seriously... I didnt fix it because you getting laid isnt important enough for me."); + return false; + } + // Attempt to place wall piece + + wallPiece = createStructure(player, wall, serverZone); + + if (wallPiece == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + continue; + } + + // walls are auto protected + wallPiece.setProtectionState(ProtectionState.PROTECTED); + PlaceAssetMsg.sendPlaceAssetConfirmWall(origin, serverZone); + + } + + // Deduct gold from character's inventory + + + return true; + } + + private Building createStructure(PlayerCharacter playerCharacter, PlacementInfo buildingInfo, Zone currentZone) { + + Blueprint blueprint; + Building newMesh; + DateTime completionDate; + float vendorRotation; + float buildingRotation; + + blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID()); + + if (blueprint == null) { + Logger.error("CreateStructure: DB returned null blueprint."); + return null; + } + + // All siege buildings build in 15 minutes + if ((blueprint.getBuildingGroup().equals(BuildingGroup.SIEGETENT)) + || (blueprint.getBuildingGroup().equals(BuildingGroup.BULWARK))) + completionDate = DateTime.now().plusMinutes(15); + else + completionDate = DateTime.now().plusHours(blueprint.getRankTime(1)); + + Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone)); + + buildingRotation = buildingInfo.getRot().y; + vendorRotation = buildingInfo.getW(); + + // if W return is negative, this is a -90 rotation not a 90? + + newMesh = DbManager.BuildingQueries.CREATE_BUILDING( + currentZone.getObjectUUID(), playerCharacter.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0), + localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0, + completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation); + + // Make sure we have a valid mesh + if (newMesh == null) { + Logger.error("CreateStructure: DB returned null object."); + return null; + } + + newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7)); + + newMesh.setLoc(newMesh.getLoc()); + InterestManager.setObjectDirty(newMesh); + return newMesh; + + } + + // Validates that player is able to place buildings + + private boolean createShrine(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) { + + Blueprint blueprint; + Building newMesh; + Shrine newShrine; + City city; + ShrineType shrineType; + + if (player == null) + return false; + + blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID()); + + if (blueprint == null) { + Logger.error("CreateShrine: DB returned null blueprint."); + return false; + } + + shrineType = Shrine.getShrineTypeByBlueprintUUID(blueprint.getBlueprintUUID()); + + city = City.getCity(currentZone.getPlayerCityUUID()); + + if (city == null) + return false; + + if (!city.isLocationOnCityGrid(buildingInfo.getLoc())) + return false; + + Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone)); + + float buildingRotation = buildingInfo.getRot().y; + float vendorRotation = buildingInfo.getW(); + + + ArrayList shrineObjects = DbManager.ShrineQueries.CREATE_SHRINE( + currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0), + localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0, + DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation, shrineType.name()); + + if (shrineObjects == null) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + for (AbstractGameObject ago : shrineObjects) { + + switch (ago.getObjectType()) { + case Building: + newMesh = (Building) ago; + newMesh.runAfterLoad(); + newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7)); + newMesh.setLoc(newMesh.getLoc()); + InterestManager.setObjectDirty(newMesh); + break; + case Shrine: + newShrine = (Shrine) ago; + newShrine.getShrineType().addShrineToServerList(newShrine); + break; + default: + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + break; + } + } + + return true; + } + + private boolean createBarracks(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) { + + Blueprint blueprint; + Building newMesh; + Shrine newShrine; + City city; + + if (player == null) + return false; + + blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID()); + + if (blueprint == null) { + Logger.error("CreateShrine: DB returned null blueprint."); + return false; + } + + city = City.getCity(currentZone.getPlayerCityUUID()); + + if (city == null) + return false; + + if (!city.isLocationOnCityGrid(buildingInfo.getLoc())) + return false; + + Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone)); + + float buildingRotation = buildingInfo.getRot().y; + float vendorRotation = buildingInfo.getW(); + DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1)); + + + newMesh = DbManager.BuildingQueries.CREATE_BUILDING( + currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0), + localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0, + completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation); + + // Make sure we have a valid mesh + if (newMesh == null) { + Logger.error("CreateStructure: DB returned null object."); + return false; + } + + newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7)); + newMesh.setLoc(newMesh.getLoc()); + InterestManager.setObjectDirty(newMesh); + + return true; + } + + private boolean createWarehouse(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) { + + Blueprint blueprint; + Building newMesh = null; + ArrayList warehouseObjects; + + blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID()); + + if (blueprint == null) { + Logger.error("CreateWarehouse: DB returned null blueprint."); + return false; + } + + Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone)); + + float buildingRotation = buildingInfo.getRot().y; + float vendorRotation = buildingInfo.getW(); + + warehouseObjects = DbManager.WarehouseQueries.CREATE_WAREHOUSE( + currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0), + localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0, + DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation); + + if (warehouseObjects == null) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + // Load the building into the simulation + + for (AbstractGameObject ago : warehouseObjects) { + + if (ago.getObjectType() == GameObjectType.Building) { + newMesh = (Building) ago; + newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7)); + // WorldGrid.addObject(newMesh, player); + newMesh.setLoc(newMesh.getLoc()); + InterestManager.setObjectDirty(newMesh); + newMesh.runAfterLoad(); + } else if (ago.getObjectType() == GameObjectType.Warehouse) { + Warehouse warehouse = (Warehouse) ago; + City city = City.getCity(currentZone.getPlayerCityUUID()); + if (city == null) + return true; + city.setWarehouseBuildingID(newMesh.getObjectUUID()); + Warehouse.warehouseByBuildingUUID.put(newMesh.getObjectUUID(), warehouse); + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/RecommendNationMsgHandler.java b/src/engine/net/client/handlers/RecommendNationMsgHandler.java new file mode 100644 index 00000000..395bde19 --- /dev/null +++ b/src/engine/net/client/handlers/RecommendNationMsgHandler.java @@ -0,0 +1,87 @@ +package engine.net.client.handlers; + +import engine.Enum.AllianceType; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.client.ClientConnection; +import engine.net.client.msg.AllianceChangeMsg; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.RecommendNationMsg; +import engine.objects.Guild; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * protecting and unprotecting city assets + */ +public class RecommendNationMsgHandler extends AbstractClientMsgHandler { + + public RecommendNationMsgHandler() { + super(RecommendNationMsg.class); + } + + private static void RecommendNation(Guild fromGuild, Guild toGuild, RecommendNationMsg msg, ClientConnection origin) { + + // Member variable declaration + Dispatch dispatch; + + // Member variable assignment + + if (fromGuild == null) + return; + + if (toGuild == null) + return; + + AllianceType allianceType; + if (msg.getAlly() == 1) + allianceType = AllianceType.RecommendedAlly; + else + allianceType = AllianceType.RecommendedEnemy; + + if (!fromGuild.addGuildToAlliance(new AllianceChangeMsg(origin.getPlayerCharacter(), fromGuild.getObjectUUID(), toGuild.getObjectUUID(), (byte) 0, 0), allianceType, toGuild, origin.getPlayerCharacter())) + return; + String alliance = msg.getAlly() == 1 ? "ally" : "enemy"; + + ChatManager.chatGuildInfo(fromGuild, origin.getPlayerCharacter().getFirstName() + " has recommended " + toGuild.getName() + " as an " + alliance); + + // dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + // DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + RecommendNationMsg msg; + + + // Member variable assignment + + msg = (RecommendNationMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + + RecommendNationMsgHandler.RecommendNation(player.getGuild(), Guild.getGuild(msg.getGuildID()), msg, origin); + + + // dispatch = Dispatch.borrow(player, baseMsg); + // DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + + } + + +} diff --git a/src/engine/net/client/handlers/RemoveFriendHandler.java b/src/engine/net/client/handlers/RemoveFriendHandler.java new file mode 100644 index 00000000..85223ded --- /dev/null +++ b/src/engine/net/client/handlers/RemoveFriendHandler.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.RemoveFriendMessage; +import engine.objects.PlayerCharacter; +import engine.objects.PlayerFriends; + + +public class RemoveFriendHandler extends AbstractClientMsgHandler { + + public RemoveFriendHandler() { + super(RemoveFriendMessage.class); + } + + public static void HandleRemoveFriend(PlayerCharacter player, RemoveFriendMessage msg) { + + //No friends in list. Early exit. + PlayerFriends.RemoveFromFriends(player.getObjectUUID(), msg.friendID); + PlayerFriends.RemoveFromFriends(msg.friendID, player.getObjectUUID()); + + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + msg = new RemoveFriendMessage(msg.friendID); + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + + RemoveFriendMessage msg = (RemoveFriendMessage) baseMsg; + + HandleRemoveFriend(player, msg); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/RemoveFromGroupHandler.java b/src/engine/net/client/handlers/RemoveFromGroupHandler.java new file mode 100644 index 00000000..a7e7ede8 --- /dev/null +++ b/src/engine/net/client/handlers/RemoveFromGroupHandler.java @@ -0,0 +1,118 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.GroupManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.net.client.msg.group.RemoveFromGroupMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +import java.util.Set; + +public class RemoveFromGroupHandler extends AbstractClientMsgHandler { + + public RemoveFromGroupHandler() { + super(RemoveFromGroupMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + // Declar member variables + + PlayerCharacter source; + PlayerCharacter target; + RemoveFromGroupMsg msg; + Group group; + GroupUpdateMsg gim; + ClientConnection gcc; + Set groupMembers; + + // Assign member variables + + msg = (RemoveFromGroupMsg) baseMsg; + + source = SessionManager.getPlayerCharacter(origin); + + if (source == null) + return false; + + group = GroupManager.getGroup(source); + + if (group == null) + return false; + + if (group.getGroupLead() != source) // Only group lead can remove + return false; + + target = SessionManager.getPlayerCharacterByID(msg.getTargetID()); + + if (target == null) + return false; + + if (target == source) { // can't remove self, must quit + RemoveFromGroupMsg reply = new RemoveFromGroupMsg(); + reply.setResponse(1); + Dispatch dispatch = Dispatch.borrow(source, reply); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + return false; + } + + gcc = SessionManager.getClientConnection(target); + + if (gcc != null) { + + // Cleanup group window for player quiting + + groupMembers = group.getMembers(); + + for (PlayerCharacter groupMember : groupMembers) { + + if (groupMember == null) + continue; + + gim = new GroupUpdateMsg(); + gim.setGroup(group); + gim.setPlayer(target); + gim.setMessageType(3); + gim.setPlayer(groupMember); + Dispatch dispatch = Dispatch.borrow(target, gim); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + } + + // Remove from group and clean up everyone elses window + group.removeGroupMember(target); + GroupManager.removeFromGroups(target); + + gim = new GroupUpdateMsg(); + gim.setGroup(group); + gim.setMessageType(3); + gim.setPlayer(target); + group.sendUpdate(gim); + + String text = target.getFirstName() + " has left your group."; + ChatManager.chatGroupInfo(source, text); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/RepairBuildingMsgHandler.java b/src/engine/net/client/handlers/RepairBuildingMsgHandler.java new file mode 100644 index 00000000..393c54be --- /dev/null +++ b/src/engine/net/client/handlers/RepairBuildingMsgHandler.java @@ -0,0 +1,128 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.SessionManager; +import engine.gameManager.ZoneManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.RepairBuildingMsg; +import engine.net.client.msg.UpdateObjectMsg; +import engine.objects.*; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * protecting and unprotecting city assets + */ +public class RepairBuildingMsgHandler extends AbstractClientMsgHandler { + + public RepairBuildingMsgHandler() { + super(RepairBuildingMsg.class); + } + + private static void RepairBuilding(Building targetBuilding, ClientConnection origin, RepairBuildingMsg msg) { + + // Member variable declaration + + Zone serverZone; + Dispatch dispatch; + + // Member variable assignment + + if (targetBuilding == null) + return; + + if (!targetBuilding.hasFunds(BuildingManager.GetRepairCost(targetBuilding))) + return; + + PlayerCharacter pc = origin.getPlayerCharacter(); + + serverZone = ZoneManager.findSmallestZone(pc.getLoc()); + + if (serverZone.getPlayerCityUUID() == 0 && targetBuilding.getBlueprint() != null && targetBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.MINE) + return; + + + City city = City.GetCityFromCache(serverZone.getPlayerCityUUID()); + + if (city != null) { + if (city.getBane() != null && city.protectionEnforced == false) + return; + + } + + //cannot repair mines during 24/7 activity. + + if (targetBuilding.getBlueprint() != null && targetBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.MINE && Mine.getMineFromTower(targetBuilding.getObjectUUID()).isActive == true) { + return; + } + + + int maxHP = (int) targetBuilding.getMaxHitPoints(); + int repairCost = BuildingManager.GetRepairCost(targetBuilding); + int missingHealth = (int) BuildingManager.GetMissingHealth(targetBuilding); + + if (!targetBuilding.transferGold(-repairCost, false)) + return; + + targetBuilding.modifyHealth(BuildingManager.GetMissingHealth(targetBuilding), null); + + UpdateObjectMsg uom = new UpdateObjectMsg(targetBuilding, 3); + + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), uom); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + RepairBuildingMsg rbm = new RepairBuildingMsg(targetBuilding.getObjectUUID(), maxHP, missingHealth, repairCost, targetBuilding.getStrongboxValue()); + + + dispatch = Dispatch.borrow(origin.getPlayerCharacter(), rbm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + Building targetBuilding; + RepairBuildingMsg msg; + + + // Member variable assignment + + msg = (RepairBuildingMsg) baseMsg; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + + switch (msg.getType()) { + case 0: + targetBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + RepairBuilding(targetBuilding, origin, msg); + break; + + // targetBuilding.createFurniture(item.getItemBase().getUseID(), 0, msg.getFurnitureLoc(), Vector3f.ZERO, 0, player); + + + } + + + // dispatch = Dispatch.borrow(player, baseMsg); + // DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + + } + + +} diff --git a/src/engine/net/client/handlers/RequestBallListHandler.java b/src/engine/net/client/handlers/RequestBallListHandler.java new file mode 100644 index 00000000..aa206d64 --- /dev/null +++ b/src/engine/net/client/handlers/RequestBallListHandler.java @@ -0,0 +1,46 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.RequestBallListMessage; +import engine.objects.PlayerCharacter; + +public class RequestBallListHandler extends AbstractClientMsgHandler { + + public RequestBallListHandler() { + super(RequestBallListMessage.class); + } + + public static void HandleRequestBallList(PlayerCharacter player, RequestBallListMessage msg) { + //currently not handled. + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + + RequestBallListMessage msg = (RequestBallListMessage) baseMsg; + + HandleRequestBallList(player, msg); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/RequestEnterWorldHandler.java b/src/engine/net/client/handlers/RequestEnterWorldHandler.java new file mode 100644 index 00000000..18b48254 --- /dev/null +++ b/src/engine/net/client/handlers/RequestEnterWorldHandler.java @@ -0,0 +1,163 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.InterestManagement.InterestManager; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.CharacterRecord; +import engine.db.archive.DataWarehouse; +import engine.db.archive.PvpRecord; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.AbstractWorldObject; +import engine.objects.Account; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import engine.server.world.WorldServer; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which requests + * that a character enters the game world from login screen + */ + +public class RequestEnterWorldHandler extends AbstractClientMsgHandler { + + public RequestEnterWorldHandler() { + super(RequestEnterWorldMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + RequestEnterWorldMsg msg; + + msg = (RequestEnterWorldMsg) baseMsg; + + Session session = SessionManager.getSession(origin); + + if (session == null) + return true; + + PlayerCharacter player = origin.getPlayerCharacter(); + + WorldGrid.RemoveWorldObject(player); + Dispatch dispatch; + + if (player == null) { + Logger.error("Unable to find player for session" + session.getSessionID()); + origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Player not found."); + return true; + } + + player.setEnteredWorld(false); + + Account acc = SessionManager.getAccount(origin); + + if (acc.status.ordinal() < WorldServer.worldAccessLevel.ordinal() || MBServerStatics.blockLogin) { + origin.disconnect(); + return true; + } + + // Brand new character. Send the city select screen + + if (player.getLevel() == 1 && player.getBindBuildingID() == -1) { + SelectCityMsg scm = new SelectCityMsg(player, true); + dispatch = Dispatch.borrow(player, scm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + + player.resetRegenUpdateTime(); + + // Map Data + + try { + WorldDataMsg wdm = new WorldDataMsg(); + dispatch = Dispatch.borrow(player, wdm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } catch (Exception e) { + // TODO Auto-generated catch block + Logger.error("WORLDDATAMESSAGE" + e.getMessage()); + } + + // Realm Data + + try { + WorldRealmMsg wrm = new WorldRealmMsg(); + dispatch = Dispatch.borrow(player, wrm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } catch (Exception e) { + // TODO Auto-generated catch block + Logger.error("REALMMESSAGE" + e.getMessage()); + } + + // Object Data + CityDataMsg wom = new CityDataMsg(session, true); + dispatch = Dispatch.borrow(player, wom); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + player.getTimestamps().put("EnterWorld", System.currentTimeMillis()); + + if (player.getLoc().equals(Vector3fImmutable.ZERO) || System.currentTimeMillis() > player.getTimeStamp("logout") + (15 * 60 * 1000)) { + player.stopMovement(player.getBindLoc()); + player.setSafeMode(); + player.updateLocation(); + player.setRegion(AbstractWorldObject.GetRegionByWorldObject(player)); + } + + player.setTimeStamp("logout", 0); + player.respawnLock.writeLock().lock(); + try { + if (!player.isAlive()) { + Logger.info("respawning player on enter world."); + player.respawn(true, true, true); + } + + } catch (Exception e) { + Logger.error(e); + } finally { + player.respawnLock.writeLock().unlock(); + } + + + player.resetDataAtLogin(); + + InterestManager.INTERESTMANAGER.HandleLoadForEnterWorld(player); + + // If this is a brand new character... + // when they enter world is a great time to write their + // character record to the data warehouse. + + if (player.getHash() == null) { + + if (DataWarehouse.recordExists(Enum.DataRecordType.CHARACTER, player.getObjectUUID()) == false) { + CharacterRecord characterRecord = CharacterRecord.borrow(player); + DataWarehouse.pushToWarehouse(characterRecord); + } + player.setHash(); + } + + // + // We will load the kill/death lists here as data is only pertinent + // to characters actually logged into the game. + // + + player.pvpKills = PvpRecord.getCharacterPvPHistory(player.getObjectUUID(), Enum.PvpHistoryType.KILLS); + player.pvpDeaths = PvpRecord.getCharacterPvPHistory(player.getObjectUUID(), Enum.PvpHistoryType.DEATHS); + + SendOwnPlayerMsg sopm = new SendOwnPlayerMsg(SessionManager.getSession(origin)); + dispatch = Dispatch.borrow(player, sopm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/RequestGuildListHandler.java b/src/engine/net/client/handlers/RequestGuildListHandler.java new file mode 100644 index 00000000..a559ef5c --- /dev/null +++ b/src/engine/net/client/handlers/RequestGuildListHandler.java @@ -0,0 +1,55 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.ReqGuildListMsg; +import engine.net.client.msg.guild.SendGuildEntryMsg; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + + +public class RequestGuildListHandler extends AbstractClientMsgHandler { + + public RequestGuildListHandler() { + super(ReqGuildListMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + Dispatch dispatch; + + // get PlayerCharacter of person accepting invite + PlayerCharacter pc = SessionManager.getPlayerCharacter( + origin); + if (pc == null) + return true; + + if (GuildStatusController.isGuildLeader(pc.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(pc, "You do not have such authority!"); + } + SendGuildEntryMsg msg = new SendGuildEntryMsg(pc); + + + dispatch = Dispatch.borrow(pc, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/SendBallEntryHandler.java b/src/engine/net/client/handlers/SendBallEntryHandler.java new file mode 100644 index 00000000..8ed595ee --- /dev/null +++ b/src/engine/net/client/handlers/SendBallEntryHandler.java @@ -0,0 +1,46 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.SendBallEntryMessage; +import engine.objects.PlayerCharacter; + +public class SendBallEntryHandler extends AbstractClientMsgHandler { + + public SendBallEntryHandler() { + super(SendBallEntryMessage.class); + } + + public static void HandleAddBall(PlayerCharacter player, SendBallEntryMessage msg) { + //currently not handled. + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + + SendBallEntryMessage msg = (SendBallEntryMessage) baseMsg; + + HandleAddBall(player, msg); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/SwearInGuildHandler.java b/src/engine/net/client/handlers/SwearInGuildHandler.java new file mode 100644 index 00000000..8c5c6414 --- /dev/null +++ b/src/engine/net/client/handlers/SwearInGuildHandler.java @@ -0,0 +1,138 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.GuildState; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.SendGuildEntryMsg; +import engine.net.client.msg.guild.SwearInGuildMsg; +import engine.objects.City; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +public class SwearInGuildHandler extends AbstractClientMsgHandler { + + public SwearInGuildHandler() { + super(SwearInGuildMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + PlayerCharacter player; + SwearInGuildMsg swearInMsg; + Guild targetGuild; + Guild nation; + Dispatch dispatch; + + swearInMsg = (SwearInGuildMsg) baseMsg; + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + targetGuild = (Guild) DbManager.getObject(GameObjectType.Guild, swearInMsg.getGuildUUID()); + + if (targetGuild == null) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return true; + } + + nation = player.getGuild(); + + if (nation == null) { + ErrorPopupMsg.sendErrorMsg(player, "You do not belong to a guild!"); + return true; + } + + try { + if (!nation.isNation()) { + ErrorPopupMsg.sendErrorMsg(player, "Your guild is not a nation!"); + return true; + } + if (!nation.getSubGuildList().contains(targetGuild)) { + ErrorPopupMsg.sendErrorMsg(player, "Your do not have such authority!"); + return true; + } + + if (!Guild.canSwearIn(targetGuild)) { + ErrorPopupMsg.sendErrorMsg(player, targetGuild.getGuildState().name() + "cannot be sworn in"); + return true; + } + + if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(player, "Your do not have such authority!"); + return true; + } + + if (!DbManager.GuildQueries.UPDATE_PARENT(targetGuild.getObjectUUID(), nation.getObjectUUID())) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity"); + return true; + } + + switch (targetGuild.getGuildState()) { + case Petitioner: + GuildManager.updateAllGuildBinds(targetGuild, nation.getOwnedCity()); + break; + case Protectorate: + break; + default: + //shouldn't get here. + break; + } + + //update Guild state. + targetGuild.setNation(nation); + GuildManager.updateAllGuildTags(targetGuild); + targetGuild.upgradeGuildState(false); + + if (nation.getGuildState() == GuildState.Sovereign) + nation.upgradeGuildState(true); + + SendGuildEntryMsg msg = new SendGuildEntryMsg(player); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + City.lastCityUpdate = System.currentTimeMillis(); + + ArrayList guildMembers = SessionManager.getActivePCsInGuildID(nation.getObjectUUID()); + + for (PlayerCharacter member : guildMembers) { + ChatManager.chatGuildInfo(member, "Your Guild is now a Nation!"); + } + + ArrayList swornMembers = SessionManager.getActivePCsInGuildID(targetGuild.getObjectUUID()); + + for (PlayerCharacter member : swornMembers) { + ChatManager.chatGuildInfo(member, "Your Guild has sword fealty to " + nation.getName() + '.'); + } + } catch (Exception e) { + Logger.error(e.getMessage()); + return true; + } + + return true; + } + +} diff --git a/src/engine/net/client/handlers/SwearInHandler.java b/src/engine/net/client/handlers/SwearInHandler.java new file mode 100644 index 00000000..2796bbaa --- /dev/null +++ b/src/engine/net/client/handlers/SwearInHandler.java @@ -0,0 +1,78 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.net.client.msg.guild.GuildListMsg; +import engine.net.client.msg.guild.SwearInMsg; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +public class SwearInHandler extends AbstractClientMsgHandler { + + public SwearInHandler() { + super(SwearInMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + SwearInMsg msg = (SwearInMsg) baseMsg; + Dispatch dispatch; + + // get source player + PlayerCharacter source = SessionManager.getPlayerCharacter(origin); + + if (source == null) + return true; + + // get target player + PlayerCharacter target = SessionManager.getPlayerCharacterByID(msg.getTargetID()); + + if (target == null) { + ChatManager.chatGuildError(source, + "No such character found!"); + return true; + } + + if (source.getGuild() != target.getGuild()) { + ChatManager.chatGuildError(source, + "That player is not a member of " + source.getGuild().getName()); + return true; + } + + // Verify source has authority to swear in + if (GuildStatusController.isInnerCouncil(source.getGuildStatus()) == false) { + ErrorPopupMsg.sendErrorMsg(source, "Your do not have such authority!"); + return true; + } + + // Swear target in and send message to guild + target.setFullMember(true); + target.incVer(); + + ChatManager.chatGuildInfo(source, target.getFirstName() + " has been sworn in as a full member!"); + + dispatch = Dispatch.borrow(source, new GuildListMsg(source.getGuild())); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + DispatchMessage.sendToAllInRange(target, new GuildInfoMsg(target, target.getGuild(), 2)); + + return true; + } + +} diff --git a/src/engine/net/client/handlers/TaxCityMsgHandler.java b/src/engine/net/client/handlers/TaxCityMsgHandler.java new file mode 100644 index 00000000..4b9a1ef2 --- /dev/null +++ b/src/engine/net/client/handlers/TaxCityMsgHandler.java @@ -0,0 +1,146 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.InterestManagement.RealmMap; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.TaxCityMsg; +import engine.net.client.msg.ViewResourcesMessage; +import engine.objects.*; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * protecting and unprotecting city assets + */ +public class TaxCityMsgHandler extends AbstractClientMsgHandler { + + public TaxCityMsgHandler() { + super(TaxCityMsg.class); + } + + private static boolean ViewTaxes(TaxCityMsg msg, PlayerCharacter player) { + + // Member variable declaration + Building building = BuildingManager.getBuildingFromCache(msg.getGuildID()); + Guild playerGuild = player.getGuild(); + + if (building == null) { + ErrorPopupMsg.sendErrorMsg(player, "Not a valid Building!"); + return true; + } + + City city = building.getCity(); + if (city == null) { + ErrorPopupMsg.sendErrorMsg(player, "This building does not belong to a city."); + return true; + } + + if (city.getWarehouse() == null) { + ErrorPopupMsg.sendErrorMsg(player, "This city does not have a warehouse!"); + return true; + } + + + if (playerGuild == null || playerGuild.isEmptyGuild()) { + ErrorPopupMsg.sendErrorMsg(player, "You must belong to a guild to do that!"); + return true; + } + + if (playerGuild.getOwnedCity() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Your Guild needs to own a city!"); + return true; + } + + if (playerGuild.getOwnedCity().getWarehouse() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Your Guild needs to own a warehouse!"); + return true; + } + + if (playerGuild.getOwnedCity().getTOL() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot find Tree of Life for your city!"); + return true; + } + +// if (playerGuild.getOwnedCity().getTOL().getRank() != 8){ +// ErrorPopupMsg.sendErrorMsg(player, "Your City needs to Own a realm!"); +// return true; +// } + + if (playerGuild.getOwnedCity().getRealm() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for your city!"); + return true; + } + Realm targetRealm = RealmMap.getRealmForCity(city); + + if (targetRealm == null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for city you are attempting to tax!"); + return true; + } + +// if (targetRealm.getRulingCity() == null){ +// ErrorPopupMsg.sendErrorMsg(player, "Realm Does not have a ruling city!"); +// return true; +// } + +// if (targetRealm.getRulingCity().getObjectUUID() != playerGuild.getOwnedCity().getObjectUUID()){ +// ErrorPopupMsg.sendErrorMsg(player, "Your guild does not rule this realm!"); +// return true; +// } + +// if (playerGuild.getOwnedCity().getObjectUUID() == city.getObjectUUID()){ +// ErrorPopupMsg.sendErrorMsg(player, "You cannot tax your own city!"); +// return true; +// } + + + if (!GuildStatusController.isTaxCollector(player.getGuildStatus())) { + ErrorPopupMsg.sendErrorMsg(player, "You Must be a tax Collector!"); + return true; + } + + +// if (!city.isAfterTaxPeriod(DateTime.now(), player)) +// return true; + + + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + vrm.setGuild(building.getGuild()); + vrm.setWarehouseBuilding(BuildingManager.getBuildingFromCache(building.getCity().getWarehouse().getBuildingUID())); + vrm.configure(); + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + TaxCityMsg msg; + + player = origin.getPlayerCharacter(); + + + msg = (TaxCityMsg) baseMsg; + + ViewTaxes(msg, player); + + + return true; + + } + + +} diff --git a/src/engine/net/client/handlers/TaxResourcesMsgHandler.java b/src/engine/net/client/handlers/TaxResourcesMsgHandler.java new file mode 100644 index 00000000..b69a897b --- /dev/null +++ b/src/engine/net/client/handlers/TaxResourcesMsgHandler.java @@ -0,0 +1,72 @@ +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.TaxResourcesMsg; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which handles + * protecting and unprotecting city assets + */ +public class TaxResourcesMsgHandler extends AbstractClientMsgHandler { + + public TaxResourcesMsgHandler() { + super(TaxResourcesMsg.class); + } + + private static boolean TaxWarehouse(TaxResourcesMsg msg, PlayerCharacter player) { + + // Member variable declaration + Building building = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + + + if (building == null) { + ErrorPopupMsg.sendErrorMsg(player, "Not a valid Building!"); + return true; + } + + City city = building.getCity(); + if (city == null) { + ErrorPopupMsg.sendErrorMsg(player, "This building does not belong to a city."); + return true; + } + + city.TaxWarehouse(msg, player); + + + return true; + + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter player; + TaxResourcesMsg msg; + + player = origin.getPlayerCharacter(); + if (player == null) + return true; + + + msg = (TaxResourcesMsg) baseMsg; + + TaxWarehouse(msg, player); + + + return true; + + } + + +} diff --git a/src/engine/net/client/handlers/ToggleGroupSplitHandler.java b/src/engine/net/client/handlers/ToggleGroupSplitHandler.java new file mode 100644 index 00000000..42ae05f4 --- /dev/null +++ b/src/engine/net/client/handlers/ToggleGroupSplitHandler.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.GroupManager; +import engine.gameManager.SessionManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.net.client.msg.group.ToggleGroupSplitMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +public class ToggleGroupSplitHandler extends AbstractClientMsgHandler { + + public ToggleGroupSplitHandler() { + super(ToggleGroupSplitMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + PlayerCharacter source; + Group group; + boolean split; + + source = SessionManager.getPlayerCharacter(origin); + + if (source == null) + return false; + + group = GroupManager.getGroup(source); + + if (group == null) + return false; + + if (group.getGroupLead() != source) // Only group lead can toggle + return false; + + split = group.toggleSplitGold(); + + // update split button + GroupUpdateMsg gum = new GroupUpdateMsg(); + gum.setGroup(group); + gum.setMessageType(6); + + group.sendUpdate(gum); + + // Send split message + + if (split) + ChatManager.chatGroupInfo(source, "Treasure is now being split."); + else + ChatManager.chatGroupInfo(source, "Treasure is no longer being split."); + + return false; + } + +} diff --git a/src/engine/net/client/handlers/TransferAssetMsgHandler.java b/src/engine/net/client/handlers/TransferAssetMsgHandler.java new file mode 100644 index 00000000..a9ccdc4e --- /dev/null +++ b/src/engine/net/client/handlers/TransferAssetMsgHandler.java @@ -0,0 +1,99 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.TransferAssetMsg; +import engine.objects.Blueprint; +import engine.objects.Building; +import engine.objects.PlayerCharacter; + +/* + * @Author: + * @Summary: Processes application protocol message which transers + * assets between characters. + */ + +public class TransferAssetMsgHandler extends AbstractClientMsgHandler { + + public TransferAssetMsgHandler() { + super(TransferAssetMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + TransferAssetMsg transferAssetMsg = (TransferAssetMsg) baseMsg; + + int Buildingid = transferAssetMsg.getObjectID(); + int BuildingType = transferAssetMsg.getObjectType(); //ToDue Later + int TargetID = transferAssetMsg.getTargetID(); + int TargetType = transferAssetMsg.getTargetType(); //ToDue later + + Building building = BuildingManager.getBuildingFromCache(Buildingid); + PlayerCharacter newOwner = PlayerCharacter.getFromCache(TargetID); + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null || building == null || newOwner == null) + return true; + + Blueprint blueprint = building.getBlueprint(); + + if (blueprint == null) + return true; + + if (building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.MINE) { + ErrorPopupMsg.sendErrorMsg(player, "You cannot transfer a mine!"); + return true; + } + + // Players cannot transfer shrines + + if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SHRINE)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer shrine!"); + return true; + } + + if (Blueprint.isMeshWallPiece(building.getBlueprintUUID())) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer fortress asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BARRACK)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer fortress asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BULWARK)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer siege asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SIEGETENT)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer siege asset!"); + return true; + } + + if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BANESTONE)) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer banestone!"); + return true; + } + if (building.getOwnerUUID() != player.getObjectUUID()) { + ChatManager.chatSystemError(player, "You do not own this asset."); + return true; + } + + if (building.getOwnerUUID() == newOwner.getObjectUUID()) { + ChatManager.chatSystemError(player, "You already own this asset."); + return true; + } + + building.setOwner(newOwner); + return true; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/handlers/TransferGoldToFromBuildingMsgHandler.java b/src/engine/net/client/handlers/TransferGoldToFromBuildingMsgHandler.java new file mode 100644 index 00000000..70ba7486 --- /dev/null +++ b/src/engine/net/client/handlers/TransferGoldToFromBuildingMsgHandler.java @@ -0,0 +1,115 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.PlaceAssetMsg; +import engine.net.client.msg.TransferGoldToFromBuildingMsg; +import engine.net.client.msg.UpdateGoldMsg; +import engine.objects.Building; +import engine.objects.CharacterItemManager; +import engine.objects.Item; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +/* + * @Author: + * @Summary: Processes application protocol message which transfers + * gold between a building's strongbox and a player character. + */ + +public class TransferGoldToFromBuildingMsgHandler extends AbstractClientMsgHandler { + + public TransferGoldToFromBuildingMsgHandler() { + super(TransferGoldToFromBuildingMsg.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter player; + Building building; + CharacterItemManager itemMan; + Item goldItem; + TransferGoldToFromBuildingMsg msg; + Dispatch dispatch; + + player = SessionManager.getPlayerCharacter(origin); + + if (player == null) + return true; + + msg = (TransferGoldToFromBuildingMsg) baseMsg; + + building = BuildingManager.getBuildingFromCache(msg.getObjectID()); + + if (building == null) + return true; + + if (msg.getDirection() == 2) { + + if (!ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building)) + return true; + if (building.setReserve(msg.getUnknown01(), player)) { + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + return true; + } + + // if (building.getTimeStamp(MBServerStatics.STRONGBOX_DELAY_STRING) > System.currentTimeMillis()){ + // ErrorPopupMsg.sendErrorMsg(player, MBServerStatics.STRONGBOX_DELAY_OUTPUT); + // return true; + // } + + //building.getTimestamps().put(MBServerStatics.STRONGBOX_DELAY_STRING, System.currentTimeMillis() + MBServerStatics.ONE_MINUTE); + + itemMan = player.getCharItemManager(); + + goldItem = itemMan.getGoldInventory(); + + if (goldItem == null) { + Logger.error("Could not access gold item"); + return true; + } + + + // Update in-game gold values for player and building + + + try { + + + if (!itemMan.transferGoldToFromBuilding(msg.getAmount(), building)) + return true; + + + UpdateGoldMsg ugm = new UpdateGoldMsg(player); + ugm.configure(); + dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + // Refresh the player's inventory if it's currently open + + // Refresh the tree's window to update strongbox + + + msg.setAmount(building.getStrongboxValue()); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } catch (Exception e) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + } + + return true; + } + +} diff --git a/src/engine/net/client/handlers/UpdateFriendStatusHandler.java b/src/engine/net/client/handlers/UpdateFriendStatusHandler.java new file mode 100644 index 00000000..90ae49f4 --- /dev/null +++ b/src/engine/net/client/handlers/UpdateFriendStatusHandler.java @@ -0,0 +1,83 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.handlers; + +import engine.Enum.DispatchChannel; +import engine.Enum.FriendStatus; +import engine.exception.MsgSendException; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.UpdateFriendStatusMessage; +import engine.objects.PlayerCharacter; +import engine.objects.PlayerFriends; +import org.pmw.tinylog.Logger; + +import java.util.HashSet; + +public class UpdateFriendStatusHandler extends AbstractClientMsgHandler { + + public UpdateFriendStatusHandler() { + super(UpdateFriendStatusMessage.class); + } + + //change to Request + public static void HandleUpdateFriend(PlayerCharacter player, UpdateFriendStatusMessage msg) { + FriendStatus friendStatus = FriendStatus.Available; + + try { + friendStatus = FriendStatus.values()[msg.statusType]; + } catch (Exception e) { + Logger.error(e); + } + player.friendStatus = friendStatus; + SendUpdateToFriends(player); + } + + public static void SendUpdateToFriends(PlayerCharacter player) { + + HashSet friends = PlayerFriends.PlayerFriendsMap.get(player.getObjectUUID()); + + if (friends == null) + return; + + UpdateFriendStatusMessage outMsg = new UpdateFriendStatusMessage(player); + + for (int friendID : friends) { + PlayerCharacter playerFriend = SessionManager.getPlayerCharacterByID(friendID); + if (playerFriend == null) + return; + Dispatch dispatch = Dispatch.borrow(playerFriend, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + + + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, + ClientConnection origin) throws MsgSendException { + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) + return true; + + + UpdateFriendStatusMessage msg = (UpdateFriendStatusMessage) baseMsg; + + + HandleUpdateFriend(player, msg); + + return true; + } +} diff --git a/src/engine/net/client/handlers/UpgradeAssetMsgHandler.java b/src/engine/net/client/handlers/UpgradeAssetMsgHandler.java new file mode 100644 index 00000000..3638c2ec --- /dev/null +++ b/src/engine/net/client/handlers/UpgradeAssetMsgHandler.java @@ -0,0 +1,153 @@ +package engine.net.client.handlers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +import java.time.LocalDateTime; + +import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup; + +/* + * + * @Summary: Processes application protocol message where a + * client requests that a building be upgraded. + */ +public class UpgradeAssetMsgHandler extends AbstractClientMsgHandler { + + // Constructor + public UpgradeAssetMsgHandler() { + + super(UpgradeAssetMessage.class); + } + + @Override + protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException { + + // Member variable declaration + + UpgradeAssetMessage msg; + ManageCityAssetsMsg outMsg; + PlayerCharacter player; + int buildingUUID; + Building buildingToRank; + LocalDateTime dateToUpgrade; + int nextRank; + int rankCost; + Dispatch dispatch; + + // Assign member variables + + msg = (UpgradeAssetMessage) baseMsg; + + // Grab pointer to the requesting player + + player = SessionManager.getPlayerCharacter(origin); + + // Grab pointer to the building from the cache + + buildingUUID = msg.getBuildingUUID(); + + buildingToRank = (Building) DbManager.getObject(GameObjectType.Building, buildingUUID); + + // Early exit if building not in cache. + + if (buildingToRank == null) { + Logger.error("Attempt to upgrade null building by " + player.getName()); + return true; + } + + // Early exit for building that is already ranking + + if (buildingToRank.isRanking()) { + Logger.error("Attempt to upgrade a building already ranking by " + player.getName()); + return true; + } + + // Calculate and set time/cost to upgrade + + nextRank = (buildingToRank.getRank() + 1); + + if (buildingToRank.getBlueprint() == null) + return true; + if (buildingToRank.getBlueprint().getMaxRank() < nextRank || nextRank == 8) { + ErrorPopupMsg.sendErrorMsg(player, "Building is already at it's Max rank."); + return true; + } + + rankCost = buildingToRank.getBlueprint().getRankCost(nextRank); + + // SEND NOT ENOUGH GOLD ERROR + + if (!buildingToRank.hasFunds(rankCost)) { + ErrorPopupMsg.sendErrorPopup(player, 127); // Not enough gold in strongbox + return true; + } + + if (rankCost > buildingToRank.getStrongboxValue()) { + sendErrorPopup(player, 127); + return true; + } + + // Validation appears good. Let's now process the upgrade + + try { + if (buildingToRank.getCity() != null) { + buildingToRank.getCity().transactionLock.writeLock().lock(); + try { + if (!buildingToRank.transferGold(-rankCost, false)) { + sendErrorPopup(player, 127); + return true; + } + } catch (Exception e) { + Logger.error(e); + } finally { + buildingToRank.getCity().transactionLock.writeLock().unlock(); + } + } else if (!buildingToRank.transferGold(-rankCost, false)) { + sendErrorPopup(player, 127); + return true; + } + + dateToUpgrade = LocalDateTime.now().plusHours(buildingToRank.getBlueprint().getRankTime(nextRank)); + + BuildingManager.setUpgradeDateTime(buildingToRank, dateToUpgrade, 0); + + // Schedule upgrade job + + BuildingManager.submitUpgradeJob(buildingToRank); + + // Refresh the client's manage asset window + // *** Refactor : We have some of these unknowns + + outMsg = new ManageCityAssetsMsg(player, buildingToRank); + + // Action TYPE + outMsg.actionType = 3; + outMsg.setTargetType(buildingToRank.getObjectType().ordinal()); + outMsg.setTargetID(buildingToRank.getObjectUUID()); + outMsg.setTargetType3(buildingToRank.getObjectType().ordinal()); + outMsg.setTargetID3(buildingToRank.getObjectUUID()); + outMsg.setAssetName1(buildingToRank.getName()); + outMsg.setUnknown54(1); + + dispatch = Dispatch.borrow(player, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } catch (Exception e) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + } + + return true; + } +} diff --git a/src/engine/net/client/msg/AbandonAssetMsg.java b/src/engine/net/client/msg/AbandonAssetMsg.java new file mode 100644 index 00000000..51e1b19a --- /dev/null +++ b/src/engine/net/client/msg/AbandonAssetMsg.java @@ -0,0 +1,75 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class AbandonAssetMsg extends ClientNetMsg { + + private int pad = 0; + private int objectType; + private int objectUUID; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AbandonAssetMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ABANDONASSET, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.pad = reader.getInt(); + this.objectType = reader.getInt(); + this.objectUUID = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.pad); + writer.putInt(this.objectType); + writer.putInt(this.objectUUID); + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getUUID() { + return objectUUID; + + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } +} diff --git a/src/engine/net/client/msg/AcceptFriendMsg.java b/src/engine/net/client/msg/AcceptFriendMsg.java new file mode 100644 index 00000000..b309e78e --- /dev/null +++ b/src/engine/net/client/msg/AcceptFriendMsg.java @@ -0,0 +1,64 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class AcceptFriendMsg extends ClientNetMsg { + + public String sourceName; + public String friendName; + + /** + * This is the general purpose constructor. + */ + public AcceptFriendMsg(PlayerCharacter pc) { + super(Protocol.FRIENDACCEPT); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AcceptFriendMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.FRIENDACCEPT, origin, reader); + } + + /** + * Copy constructor + */ + public AcceptFriendMsg(AcceptFriendMsg msg) { + super(Protocol.FRIENDACCEPT); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //Do we even want to try this? + this.sourceName = reader.getString(); //This is source name.. this friends list must never been updated since launch, not using IDS. + this.friendName = reader.getString(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.sourceName); + writer.putString(this.friendName); + } +} diff --git a/src/engine/net/client/msg/AcceptTradeRequestMsg.java b/src/engine/net/client/msg/AcceptTradeRequestMsg.java new file mode 100644 index 00000000..e0f63f5e --- /dev/null +++ b/src/engine/net/client/msg/AcceptTradeRequestMsg.java @@ -0,0 +1,129 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Accept trade request + * + * @author Eighty + */ +public class AcceptTradeRequestMsg extends ClientNetMsg { + + private int unknown01; //pad? + private int playerType; + private int playerID; + private int targetType; + private int targetID; + + + /** + * This is the general purpose constructor + */ + public AcceptTradeRequestMsg(int unknown01, AbstractGameObject player, AbstractGameObject target) { + super(Protocol.REQUESTTRADEOK); + this.unknown01 = unknown01; + this.playerType = player.getObjectType().ordinal(); + this.playerID = player.getObjectUUID(); + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AcceptTradeRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTTRADEOK, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + playerType = reader.getInt(); + playerID = reader.getInt(); + targetType = reader.getInt(); + targetID = reader.getInt(); + + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putInt(playerType); + writer.putInt(playerID); + writer.putInt(targetType); + writer.putInt(targetID); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + +} diff --git a/src/engine/net/client/msg/AckBankWindowOpenedMsg.java b/src/engine/net/client/msg/AckBankWindowOpenedMsg.java new file mode 100644 index 00000000..c3a98d35 --- /dev/null +++ b/src/engine/net/client/msg/AckBankWindowOpenedMsg.java @@ -0,0 +1,91 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + +/** + * Bank window opened + * + * @author Burfo + */ +public class AckBankWindowOpenedMsg extends ClientNetMsg { + + private int playerType; + private int playerID; + private long unknown01; // possibly NPC ID? + private long unknown02; + + /** + * This is the general purpose constructor. + */ + public AckBankWindowOpenedMsg(PlayerCharacter pc, long unknown01, long unknown02) { + super(Protocol.COSTTOOPENBANK); + this.playerType = pc.getObjectType().ordinal(); + this.playerID = pc.getObjectUUID(); + this.unknown01 = unknown01; + this.unknown02 = unknown02; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AckBankWindowOpenedMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.COSTTOOPENBANK, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + playerType = reader.getInt(); + playerID = reader.getInt(); + unknown01 = reader.getLong(); + unknown02 = reader.getLong(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(playerType); + writer.putInt(playerID); + writer.putLong(unknown01); + writer.putLong(unknown02); + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + +} diff --git a/src/engine/net/client/msg/ActivateNPCMessage.java b/src/engine/net/client/msg/ActivateNPCMessage.java new file mode 100644 index 00000000..985acbf9 --- /dev/null +++ b/src/engine/net/client/msg/ActivateNPCMessage.java @@ -0,0 +1,158 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Item; + +import java.util.ArrayList; + +public class ActivateNPCMessage extends ClientNetMsg { + + private ArrayList ItemList; + private int unknown01; + private int unknown02; + private int buildingUUID; + private int unknown03; + private int contractItem; + private int unknown05; + private int size; + + /** + * This is the general purpose constructor. + */ + public ActivateNPCMessage() { + this(new ArrayList<>()); + } + + /** + * This is the general purpose constructor. + */ + public ActivateNPCMessage(ArrayList items) { + super(Protocol.ACTIVATENPC); + this.unknown01 = 0; + this.size = 0; + this.ItemList = items; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ActivateNPCMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ACTIVATENPC, origin, reader); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + //Larger size for historically larger opcodes + return (16); // 2^16 == 64k + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + for (int i = 0; i < 6; i++) { + writer.putInt(0); + } + writer.putInt(this.size); + for (Item item : this.ItemList) { + writer.putInt(item.getObjectType().ordinal()); + writer.putInt(item.getObjectUUID()); + + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + unknown02 = reader.getInt(); + reader.getInt(); // Object Type Padding + buildingUUID = reader.getInt(); + unknown03 = reader.getInt(); + contractItem = reader.getInt(); + unknown05 = reader.getInt(); + + } + + // TODO fix ArrayList Accessability. + + public int size() { + return this.ItemList.size(); + } + + public int getUnknown01() { + return unknown01; + } + + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + public int getUnknown02() { + return unknown02; + } + + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + public int getUnknown03() { + return unknown03; + } + + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + public int getContractItem() { + return contractItem; + } + + public void setContractItem(int contractItem) { + this.contractItem = contractItem; + } + + public int getUnknown05() { + return unknown05; + } + + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + public int buildingUUID() { + return buildingUUID; + } + + public void setSize(int size) { + this.size = size; + } + + public void setItemList(ArrayList value) { + this.ItemList = value; + } + +} diff --git a/src/engine/net/client/msg/AddFriendMessage.java b/src/engine/net/client/msg/AddFriendMessage.java new file mode 100644 index 00000000..f84010b5 --- /dev/null +++ b/src/engine/net/client/msg/AddFriendMessage.java @@ -0,0 +1,66 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class AddFriendMessage extends ClientNetMsg { + + public PlayerCharacter friend; + + /** + * This is the general purpose constructor. + */ + public AddFriendMessage(PlayerCharacter pc) { + super(Protocol.ADDFRIEND); + friend = pc; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AddFriendMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ADDFRIEND, origin, reader); + } + + /** + * Copy constructor + */ + public AddFriendMessage(AddFriendMessage msg) { + super(Protocol.ADDFRIEND); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + //message is serialize only, no need for deserialize. + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(friend.getObjectUUID()); + writer.putString(friend.getName()); + writer.putInt(0); //possibly available, busy, away? + writer.putInt(0); + } +} diff --git a/src/engine/net/client/msg/AddGoldToTradeWindowMsg.java b/src/engine/net/client/msg/AddGoldToTradeWindowMsg.java new file mode 100644 index 00000000..ee1ab4ff --- /dev/null +++ b/src/engine/net/client/msg/AddGoldToTradeWindowMsg.java @@ -0,0 +1,95 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class AddGoldToTradeWindowMsg extends ClientNetMsg { + + private int unknown01; + private long playerCompID; + private int amount; + + /** + * This is the general purpose constructor + */ + public AddGoldToTradeWindowMsg(int unknown01, long playerCompID, int amount) { + super(Protocol.TRADEADDGOLD); + this.unknown01 = unknown01; + this.playerCompID = playerCompID; + this.amount = amount; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AddGoldToTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRADEADDGOLD, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + playerCompID = reader.getLong(); + amount = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putLong(playerCompID); + writer.putInt(amount); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the amount + */ + public int getAmount() { + return amount; + } + + /** + * @param amount the amount to set + */ + public void setAmount(int amount) { + this.amount = amount; + } + +} diff --git a/src/engine/net/client/msg/AddItemToTradeWindowMsg.java b/src/engine/net/client/msg/AddItemToTradeWindowMsg.java new file mode 100644 index 00000000..c66c4b9b --- /dev/null +++ b/src/engine/net/client/msg/AddItemToTradeWindowMsg.java @@ -0,0 +1,115 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +public class AddItemToTradeWindowMsg extends ClientNetMsg { + + private int unknown01; + private int playerType; + private int playerID; + private int itemID; + private int itemType; + + + /** + * This is the general purpose constructor + */ + public AddItemToTradeWindowMsg(int unknown01, AbstractGameObject player, AbstractGameObject item) { + super(Protocol.TRADEADDOBJECT); + this.unknown01 = unknown01; + this.playerType = player.getObjectType().ordinal(); + this.playerID = player.getObjectUUID(); + this.itemType = item.getObjectType().ordinal(); + this.itemID = item.getObjectUUID(); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AddItemToTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRADEADDOBJECT, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + playerType = reader.getInt(); + playerID = reader.getInt(); + itemType = reader.getInt(); + itemID = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putInt(playerType); + writer.putInt(playerID); + writer.putInt(itemType); + writer.putInt(itemID); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } + + public int getItemID() { + return itemID; + } + + public void setItemID(int itemID) { + this.itemID = itemID; + } + +} diff --git a/src/engine/net/client/msg/AllianceChangeMsg.java b/src/engine/net/client/msg/AllianceChangeMsg.java new file mode 100644 index 00000000..cab833c4 --- /dev/null +++ b/src/engine/net/client/msg/AllianceChangeMsg.java @@ -0,0 +1,140 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class AllianceChangeMsg extends ClientNetMsg { + public final static int MAKE_ENEMY = 4; + public final static int MAKE_ALLY = 6; + public final static int REMOVE = 7; + public final static byte INFO_SUCCESS = 0; + public final static byte ERROR_NOT_RECOMMENDED = 1; + public final static byte ERROR_NOT_SAME_GUILD = 2; + public final static byte ERROR_NOT_AUTHORIZED = 4; + public final static byte ERROR_NOT_SAME_FACTION = 7; + public final static byte ERROR_TOO_MANY = 13; + public final static byte ERROR_TO0_EARLY = 14; + private byte msgType; + private int sourceGuildID; + private int targetGuildID; + private int secondsToWait; + private boolean ally; + + + public AllianceChangeMsg(PlayerCharacter player, int sourceGuildID, int targetGuildID, byte msgType, int secondsToWait) { + super(Protocol.ALLIANCECHANGE); + this.sourceGuildID = sourceGuildID; + this.targetGuildID = targetGuildID; + this.msgType = msgType; + this.secondsToWait = secondsToWait; + + } + + public AllianceChangeMsg() { + super(Protocol.ALLIANCECHANGE); + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AllianceChangeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ALLIANCECHANGE, origin, reader); + } + //CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD. + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.msgType = reader.get(); + + switch (this.msgType) { + case 1: + case 2: + case 3: + case MAKE_ENEMY: + case MAKE_ALLY: + case REMOVE: + case 5: + reader.getInt(); //source guild type; + this.sourceGuildID = reader.getInt(); + reader.getInt(); + this.targetGuildID = reader.getInt(); + break; + + + } + + + } + + + // Precache and configure this message before we serialize it + + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.put(this.msgType); + if (this.msgType == ERROR_TO0_EARLY) + writer.putInt(this.secondsToWait); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.sourceGuildID); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.targetGuildID); + + + } + + + public int getSecondsToWait() { + return secondsToWait; + } + + public void setSecondsToWait(int secondsToWait) { + this.secondsToWait = secondsToWait; + } + + public int getMsgType() { + return msgType; + } + + public void setMsgType(byte msgType) { + this.msgType = msgType; + } + + public int getSourceGuildID() { + return sourceGuildID; + } + + public int getTargetGuildID() { + return targetGuildID; + } + + public boolean isAlly() { + return ally; + } +} diff --git a/src/engine/net/client/msg/AllyEnemyListMsg.java b/src/engine/net/client/msg/AllyEnemyListMsg.java new file mode 100644 index 00000000..7e335049 --- /dev/null +++ b/src/engine/net/client/msg/AllyEnemyListMsg.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Guild; +import engine.objects.GuildAlliances; +import engine.objects.GuildTag; +import engine.objects.PlayerCharacter; + + +public class AllyEnemyListMsg extends ClientNetMsg { + + + private int guildID; + + + public AllyEnemyListMsg(PlayerCharacter player) { + super(Protocol.ALLYENEMYLIST); + this.guildID = player.getGuildUUID(); + + } + + public AllyEnemyListMsg() { + super(Protocol.ALLYENEMYLIST); + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AllyEnemyListMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ALLYENEMYLIST, origin, reader); + } + //CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD. + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + this.guildID = reader.getInt(); + } + + + // Precache and configure this message before we serialize it + + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.guildID); + + Guild guild = Guild.getGuild(this.guildID); + + writer.putInt(guild.getAllyList().size()); + + for (Guild ally : guild.getAllyList()) { + writer.putInt(GameObjectType.Guild.ordinal());//guildType + writer.putInt(ally.getObjectUUID());//GuildID + writer.putString(ally.getName()); + GuildTag._serializeForDisplay(ally.getGuildTag(), writer); + writer.put((byte) 0); + } + + + writer.putInt(guild.getEnemyList().size()); + + for (Guild enemy : guild.getEnemyList()) { + writer.putInt(GameObjectType.Guild.ordinal());//guildType + writer.putInt(enemy.getObjectUUID());//GuildID + writer.putString(enemy.getName()); + GuildTag._serializeForDisplay(enemy.getGuildTag(), writer); + writer.put((byte) 1); + } + + + writer.putInt(guild.getRecommendList().size()); + for (Guild recommended : guild.getRecommendList()) { + + GuildAlliances guildAlliance = guild.guildAlliances.get(recommended.getObjectUUID()); + writer.putInt(GameObjectType.Guild.ordinal());//guildType + writer.putInt(recommended.getObjectUUID());//GuildID + writer.putString(recommended.getName()); + GuildTag._serializeForDisplay(recommended.getGuildTag(), writer); + writer.put((byte) 1); // ? + writer.putString(guildAlliance.getRecommender()); // recommender name. + writer.put((byte) (guildAlliance.isAlly() ? 1 : 0)); //ally 1 enemy 0 + + } + + writer.put((byte) 1); + + } + + public int getGuildID() { + return guildID; + } + +} diff --git a/src/engine/net/client/msg/ApplyBuildingEffectMsg.java b/src/engine/net/client/msg/ApplyBuildingEffectMsg.java new file mode 100644 index 00000000..f1b221fa --- /dev/null +++ b/src/engine/net/client/msg/ApplyBuildingEffectMsg.java @@ -0,0 +1,110 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ApplyBuildingEffectMsg extends ClientNetMsg { + + protected int unknown01; + protected int unknown02; + protected int buildingType; + protected int buildingID; + protected int unknown03; + + /** + * This is the general purpose constructor. + */ + public ApplyBuildingEffectMsg() { + super(Protocol.VISUALUPDATE); + } + + public ApplyBuildingEffectMsg(int unknown01, int unknown02, int buildingType, int buildingID, int unknown03) { + super(Protocol.VISUALUPDATE); + this.unknown01 = unknown01; + this.unknown02 = unknown02; + this.buildingType = buildingType; + this.buildingID = buildingID; + this.unknown03 = unknown03; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ApplyBuildingEffectMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.VISUALUPDATE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + if (this.unknown02 == 0) { + writer.putInt(this.unknown03); + writer.putInt(this.buildingType); + writer.putInt(this.buildingID); + writer.putInt(0); + return; + } + writer.putInt(this.buildingType); + writer.putInt(this.buildingID); + writer.putInt(this.unknown03); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + public int getUnknown01() { + return unknown01; + } + + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + public int getUnknown02() { + return unknown02; + } + + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public int getUnknown03() { + return unknown03; + } + + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } +} diff --git a/src/engine/net/client/msg/ApplyEffectMsg.java b/src/engine/net/client/msg/ApplyEffectMsg.java new file mode 100644 index 00000000..b0179ce6 --- /dev/null +++ b/src/engine/net/client/msg/ApplyEffectMsg.java @@ -0,0 +1,262 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ApplyEffectMsg extends ClientNetMsg { + + protected int numTrains; + protected int effectID; + protected int sourceType; + protected int sourceID; + protected int targetType; + protected int targetID; + + protected int unknown02; + protected int unknown03; + protected int duration; + protected int unknown05; + protected byte unknown06; + + protected int powerUsedID; + + protected String powerUsedName; + private int effectSourceType = 0; + private int effectSourceID = 0; + + /** + * This is the general purpose constructor. + */ + public ApplyEffectMsg() { + super(Protocol.POWERACTION); + this.numTrains = 0; + this.effectID = 0; + this.sourceType = 0; + this.sourceID = 0; + this.targetType = 0; + this.targetID = 0; + + this.unknown02 = 0; + this.unknown03 = 0; + this.duration = 0; + this.unknown05 = 0; + this.unknown06 = (byte) 0; + + this.powerUsedID = 0; + this.powerUsedName = ""; + } + + /** + * This is the general purpose constructor. + */ + public ApplyEffectMsg(AbstractWorldObject source, AbstractWorldObject target, int numTrains, int effectID, int duration, + int powerUsedID, String powerUsedName) { + super(Protocol.POWERACTION); + this.numTrains = numTrains; + this.effectID = effectID; + + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + } else { + this.sourceType = 0; + this.sourceID = 0; + } + + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + } else { + this.targetType = 0; + this.targetID = 0; + } + this.unknown02 = 0; + this.unknown03 = 0; + this.duration = duration; + this.unknown05 = 0; + this.unknown06 = (byte) 0; + + this.powerUsedID = powerUsedID; + this.powerUsedName = powerUsedName; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ApplyEffectMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.POWERACTION, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.numTrains); + writer.putInt(this.effectID); + + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.duration); + writer.putInt(this.unknown05); + writer.put(this.unknown06); + + if (this.unknown06 == (byte) 1) { + writer.putInt(this.effectSourceType); + writer.putInt(this.effectSourceID); + } else { + writer.putInt(this.powerUsedID); + } + + writer.putString(this.powerUsedName); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.numTrains = reader.getInt(); + this.effectID = reader.getInt(); + + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.duration = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.get(); + + this.powerUsedID = reader.getInt(); + + this.powerUsedName = reader.getString(); + } + + public int getNumTrains() { + return this.numTrains; + } + + public void setNumTrains(int value) { + this.numTrains = value; + } + + public int getEffectID() { + return this.effectID; + } + + public void setEffectID(int value) { + this.effectID = value; + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public int getTargetType() { + return this.targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return this.targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public int getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getUnknown03() { + return this.unknown03; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public int getDuration() { + return this.duration; + } + + public void setDuration(int value) { + this.duration = value; + } + + public int getUnknown05() { + return this.unknown05; + } + + public void setUnknown05(int value) { + this.unknown05 = value; + } + + public byte getUnknown06() { + return this.unknown06; + } + + public void setUnknown06(byte value) { + this.unknown06 = value; + } + + public void setPowerUsedID(int value) { + this.powerUsedID = value; + } + + public void setPowerUsedName(String value) { + this.powerUsedName = value; + } + + public void setEffectSourceType(int effectSourceType) { + this.effectSourceType = effectSourceType; + } + + public void setEffectSourceID(int effectSourceID) { + this.effectSourceID = effectSourceID; + } +} diff --git a/src/engine/net/client/msg/ApplyRuneMsg.java b/src/engine/net/client/msg/ApplyRuneMsg.java new file mode 100644 index 00000000..6c6b893f --- /dev/null +++ b/src/engine/net/client/msg/ApplyRuneMsg.java @@ -0,0 +1,393 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.net.*; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.CharacterRune; +import engine.objects.PlayerCharacter; +import engine.objects.RuneBase; +import engine.objects.RuneBaseAttribute; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class ApplyRuneMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + private int removeRuneBase; + private int runeBase; + private int runeType; + private int runeID; + private Boolean isPromo; + + /** + * This is the general purpose constructor. + */ + public ApplyRuneMsg(int targetType, int targetID, int runeBase, int runeType, int runeID, Boolean isPromo) { + super(Protocol.SETRUNE); + this.targetType = targetType; + this.targetID = targetID; + this.runeBase = runeBase; + this.runeType = runeType; + this.runeID = runeID; + this.isPromo = isPromo; + this.removeRuneBase = 0; + } + + public ApplyRuneMsg(int targetType, int targetID, int removeRuneBase) { + super(Protocol.SETRUNE); + this.targetType = targetType; + this.targetID = targetID; + this.runeBase = 0; + this.runeType = 0; + this.runeID = 0; + this.isPromo = false; + this.removeRuneBase = removeRuneBase; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ApplyRuneMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SETRUNE, origin, reader); + } + + public static boolean applyRune(int runeID, ClientConnection origin, PlayerCharacter playerCharacter) { + + RuneBase rb = RuneBase.getRuneBase(runeID); + Dispatch dispatch; + + if (playerCharacter == null || origin == null || rb == null) { + return false; + } + + //Check race is met + ConcurrentHashMap races = rb.getRace(); + if (races.size() > 0) { + int raceID = playerCharacter.getRaceID(); + boolean valid = false; + for (int validID : races.keySet()) { + if (validID == raceID) { + valid = true; + break; + } + } + if (!valid) { + return false; + } + } + + //Check base class is met + ConcurrentHashMap baseClasses = rb.getBaseClass(); + if (baseClasses.size() > 0) { + int baseClassID = playerCharacter.getBaseClassID(); + boolean valid = false; + for (int validID : baseClasses.keySet()) { + if (validID == baseClassID) { + valid = true; + break; + } + } + if (!valid) { + return false; + } + } + + //Check promotion class is met + ConcurrentHashMap promotionClasses = rb.getPromotionClass(); + if (promotionClasses.size() > 0) { + int promotionClassID = playerCharacter.getPromotionClassID(); + boolean valid = false; + for (int validID : promotionClasses.keySet()) { + if (validID == promotionClassID) { + valid = true; + break; + } + } + if (!valid) { + return false; + } + } + + //Check disciplines are met + ArrayList runes = playerCharacter.getRunes(); + ConcurrentHashMap disciplines = rb.getDiscipline(); + if (disciplines.size() > 0) { + for (CharacterRune cr : runes) { + int runeBaseID = cr.getRuneBaseID(); + for (Integer prohID : disciplines.keySet()) { + if (runeBaseID == prohID) { + return false; //Prohibited rune + } + } + } + } + + int discCount = 0; + for (CharacterRune cr : runes) { + int runeBaseID = cr.getRuneBaseID(); + //count number of discipline runes + if (runeBaseID > 3000 && runeBaseID < 3049) { + discCount++; + } + //see if rune is already applied + if (runeBaseID == runeID) { + return false; + } + } + + //Check level is met + if (playerCharacter.getLevel() < rb.getLevelRequired()) { + return false; + } + + int strTotal = 0; + int dexTotal = 0; + int conTotal = 0; + int intTotal = 0; + int spiTotal = 0; + int cost = 0; + + //Check any attributes are met + ArrayList attrs = rb.getAttrs(); + + if (rb.getAttrs() != null) + for (RuneBaseAttribute rba : attrs) { + int attrID = rba.getAttributeID(); + int mod = rba.getModValue(); + switch (attrID) { + case MBServerStatics.RUNE_COST_ATTRIBUTE_ID: + if (mod > playerCharacter.getUnusedStatPoints()) { + return false; + } + cost = mod; + break; + case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID: + if ((int) playerCharacter.statStrBase < mod) { + return false; + } + strTotal = mod; + break; + case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID: + if ((int) playerCharacter.statDexBase < mod) { + return false; + } + dexTotal = mod; + break; + case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID: + if ((int) playerCharacter.statConBase < mod) { + return false; + } + conTotal = mod; + break; + case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID: + if ((int) playerCharacter.statIntBase < mod) { + return false; + } + intTotal = mod; + break; + case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID: + if ((int) playerCharacter.statSpiBase < mod) { + return false; + } + spiTotal = mod; + break; + case MBServerStatics.RUNE_STR_ATTRIBUTE_ID: + strTotal += mod; + break; + case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID: + dexTotal += mod; + break; + case MBServerStatics.RUNE_CON_ATTRIBUTE_ID: + conTotal += mod; + break; + case MBServerStatics.RUNE_INT_ATTRIBUTE_ID: + intTotal += mod; + break; + case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID: + spiTotal += mod; + break; + } + } + + //Check if max number runes already reached + if (runes.size() > 12) { + return false; + } + + //if discipline, check number applied + if (isDiscipline(runeID)) { + if (playerCharacter.getLevel() < 70) { + if (discCount > 2) { + return false; + } + } else { + if (discCount > 3) { + return false; + } + } + } + + //Everything succeeded. Let's apply the rune + //Attempt add rune to database + CharacterRune runeWithoutID = new CharacterRune(rb, playerCharacter.getObjectUUID()); + CharacterRune cr; + try { + cr = DbManager.CharacterRuneQueries.ADD_CHARACTER_RUNE(runeWithoutID); + } catch (Exception e) { + cr = null; + Logger.error(e); + } + if (cr == null) { + return false; + } + + //remove any overridden runes from player + ArrayList overwrite = rb.getOverwrite(); + CharacterRune toRemove = null; + if (overwrite.size() > 0) { + for (int overwriteID : overwrite) { + toRemove = playerCharacter.removeRune(overwriteID); + } + } + + //add rune to player + playerCharacter.addRune(cr); + + // recalculate all bonuses/formulas/skills/powers + playerCharacter.recalculate(); + + //if overwriting a stat rune, add any amount granted from previous rune. + if (toRemove != null) { + RuneBase rbs = toRemove.getRuneBase(); + if (rbs != null && rbs.getObjectUUID() > 249999 && rbs.getObjectUUID() < 250045) { + //add any additional stats to match old amount + int dif = strTotal - (int) playerCharacter.statStrBase; + if (dif > 0 && strTotal < (int) playerCharacter.statStrMax) { + playerCharacter.addStr(dif); + } + dif = dexTotal - (int) playerCharacter.statDexBase; + if (dif > 0 && dexTotal < (int) playerCharacter.statDexMax) { + playerCharacter.addDex(dif); + } + dif = conTotal - (int) playerCharacter.statConBase; + if (dif > 0 && conTotal < (int) playerCharacter.statConMax) { + playerCharacter.addCon(dif); + } + dif = intTotal - (int) playerCharacter.statIntBase; + if (dif > 0 && intTotal < (int) playerCharacter.statIntMax) { + playerCharacter.addInt(dif); + } + dif = spiTotal - (int) playerCharacter.statSpiBase; + if (dif > 0 && spiTotal < (int) playerCharacter.statSpiMax) { + playerCharacter.addSpi(dif); + } + + // recalculate all bonuses/formulas/skills/powers + playerCharacter.recalculate(); + } + } + + if (cost > 0) { + ModifyStatMsg msm = new ModifyStatMsg((0 - cost), 0, 3); + dispatch = Dispatch.borrow(playerCharacter, msm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + //send apply rune message to client + ApplyRuneMsg arm = new ApplyRuneMsg(playerCharacter.getObjectType().ordinal(), playerCharacter.getObjectUUID(), runeID, cr.getObjectType().ordinal(), cr.getObjectUUID(), false); + dispatch = Dispatch.borrow(playerCharacter, arm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + //alert them of success + ErrorPopupMsg.sendErrorPopup(playerCharacter, 160); + + //reapply bonuses + playerCharacter.applyBonuses(); + + return true; + } + + public static boolean isDiscipline(int runeID) { + + return runeID > 3000 && runeID < 3050; + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(0); + writer.putInt(this.removeRuneBase); + writer.putInt(0); + writer.putInt(this.runeBase); + writer.putInt(this.runeType); + writer.putInt(this.runeID); + if (this.isPromo) { + writer.put((byte) 1); + } else { + writer.put((byte) 0); + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + reader.getInt(); + this.removeRuneBase = reader.getInt(); + reader.getInt(); + this.runeBase = reader.getInt(); + this.runeType = reader.getInt(); + this.runeID = reader.getInt(); + this.isPromo = (reader.get() == 1) ? true : false; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public int getRuneID() { + return runeID; + } + + public void setRuneID(int value) { + this.runeID = value; + } +} diff --git a/src/engine/net/client/msg/ArcLoginNotifyMsg.java b/src/engine/net/client/msg/ArcLoginNotifyMsg.java new file mode 100644 index 00000000..fd4f48df --- /dev/null +++ b/src/engine/net/client/msg/ArcLoginNotifyMsg.java @@ -0,0 +1,143 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ArcLoginNotifyMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int unknown03; + private short unknown04; + private byte unknown05; + + /** + * This is the general purpose constructor. + */ + public ArcLoginNotifyMsg() { + super(Protocol.ARCLOGINNOTIFY); + this.unknown01 = 0x40A5BDB0; + this.unknown02 = 0x342AA9F0; + this.unknown03 = 0; + this.unknown04 = (short) 0; + this.unknown05 = (byte) 0; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ArcLoginNotifyMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCLOGINNOTIFY, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putShort(this.unknown04); + writer.put(this.unknown05); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getShort(); + this.unknown05 = reader.get(); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public short getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(short unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public byte getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(byte unknown05) { + this.unknown05 = unknown05; + } + +} diff --git a/src/engine/net/client/msg/ArcMineChangeProductionMsg.java b/src/engine/net/client/msg/ArcMineChangeProductionMsg.java new file mode 100644 index 00000000..0799a9ce --- /dev/null +++ b/src/engine/net/client/msg/ArcMineChangeProductionMsg.java @@ -0,0 +1,60 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ArcMineChangeProductionMsg extends ClientNetMsg { + + private int mineID; + private int resourceHash; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ArcMineChangeProductionMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCMINECHANGEPRODUCTION, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.mineID = reader.getInt(); + this.resourceHash = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.mineID); + writer.putInt(this.resourceHash); + } + + public int getMineID() { + return this.mineID; + } + + public int getResourceHash() { + return this.resourceHash; + } + +} diff --git a/src/engine/net/client/msg/ArcMineWindowAvailableTimeMsg.java b/src/engine/net/client/msg/ArcMineWindowAvailableTimeMsg.java new file mode 100644 index 00000000..249600cd --- /dev/null +++ b/src/engine/net/client/msg/ArcMineWindowAvailableTimeMsg.java @@ -0,0 +1,110 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; +import engine.objects.Guild; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.joda.time.Seconds; + +public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg { + + private int buildingUUID; + private Building treeOfLife; + + private int currentMineHour; + private Seconds secondsLeft; + private DateTime lateTime; + private int late; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ArcMineWindowAvailableTimeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCMINEWINDOWAVAILABLETIME, origin, reader); + } + + public ArcMineWindowAvailableTimeMsg(Building treeOfLife, int timeLeft) { + super(Protocol.ARCMINEWINDOWAVAILABLETIME); + this.treeOfLife = treeOfLife; + this.buildingUUID = treeOfLife.getObjectUUID(); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); // Object type padding (We know it's a building) + this.buildingUUID = reader.getInt(); + reader.getInt(); + + } + + // Configures and pre-caches values for this message + // so everything is already available during serialisation. + + public void configure() { + + Guild guild; + guild = this.treeOfLife.getGuild(); + + if (guild != null) + currentMineHour = guild.getMineTime(); + + late = MBServerStatics.MINE_LATE_WINDOW; + lateTime = DateTime.now(); + + if (late == 0) + lateTime = lateTime.plusDays(1); + + lateTime = lateTime.hourOfDay().setCopy(late); + + late = ((late > 23) ? (late - 24) : late); + secondsLeft = Seconds.secondsBetween(DateTime.now(), lateTime); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putInt(MBServerStatics.MINE_EARLY_WINDOW); //15); + writer.putInt(late); + writer.putInt(currentMineHour); + + writer.putInt(this.treeOfLife.getObjectType().ordinal()); + writer.putInt(this.treeOfLife.getObjectUUID()); + + writer.putInt((int) secondsLeft.getSeconds()); + } + + public int getBuildingUUID() { + return buildingUUID; + } + + public void setBuildingUUID(int buildingUUID) { + this.buildingUUID = buildingUUID; + } +} diff --git a/src/engine/net/client/msg/ArcMineWindowChangeMsg.java b/src/engine/net/client/msg/ArcMineWindowChangeMsg.java new file mode 100644 index 00000000..f8b6d309 --- /dev/null +++ b/src/engine/net/client/msg/ArcMineWindowChangeMsg.java @@ -0,0 +1,70 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ArcMineWindowChangeMsg extends ClientNetMsg { + + private int time; + private int buildingType; + private int buildingID; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ArcMineWindowChangeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCMINEWINDOWCHANGE, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.time = reader.getInt(); + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.time); + writer.putInt(this.buildingType); + writer.putInt(this.buildingID); + } + + public int getTime() { + return this.time; + } + + public void setTime(int value) { + this.time = value; + } + + public int getBuildingID() { + return this.buildingID; + } + + public void setBuildingID(int value) { + this.buildingID = value; + } +} diff --git a/src/engine/net/client/msg/ArcOwnedMinesListMsg.java b/src/engine/net/client/msg/ArcOwnedMinesListMsg.java new file mode 100644 index 00000000..202dc621 --- /dev/null +++ b/src/engine/net/client/msg/ArcOwnedMinesListMsg.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Mine; + +import java.util.ArrayList; + +public class ArcOwnedMinesListMsg extends ClientNetMsg { + + ArrayList mineList = new ArrayList<>(); + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ArcOwnedMinesListMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCOWNEDMINESLIST, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(mineList.size()); + for (Mine mine : mineList) { + mine.serializeForMineProduction(writer); + } + } + + public void setMineList(ArrayList mineList) { + this.mineList = mineList; + } + +} diff --git a/src/engine/net/client/msg/ArcSiegeSpireMsg.java b/src/engine/net/client/msg/ArcSiegeSpireMsg.java new file mode 100644 index 00000000..2942fce6 --- /dev/null +++ b/src/engine/net/client/msg/ArcSiegeSpireMsg.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ArcSiegeSpireMsg extends ClientNetMsg { + + private int buildingUUID; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ArcSiegeSpireMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCSIEGESPIRE, origin, reader); + } + + public ArcSiegeSpireMsg() { + super(Protocol.ARCSIEGESPIRE); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); // object type padding + this.buildingUUID = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + } + + public int getBuildingUUID() { + return buildingUUID; + } + +} diff --git a/src/engine/net/client/msg/ArcViewAssetTransactionsMsg.java b/src/engine/net/client/msg/ArcViewAssetTransactionsMsg.java new file mode 100644 index 00000000..af4cc873 --- /dev/null +++ b/src/engine/net/client/msg/ArcViewAssetTransactionsMsg.java @@ -0,0 +1,151 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.Enum.TransactionType; +import engine.exception.SerializationException; +import engine.gameManager.BuildingManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; + +import java.util.ArrayList; + +public class ArcViewAssetTransactionsMsg extends ClientNetMsg { + + Building warehouseBuilding; + private int warehouseID; + private Warehouse warehouse; + private int transactionID; + private ArrayList transactions; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ArcViewAssetTransactionsMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCVIEWASSETTRANSACTIONS, origin, reader); + } + + public ArcViewAssetTransactionsMsg(Warehouse warehouse, ArcViewAssetTransactionsMsg msg) { + super(Protocol.ARCVIEWASSETTRANSACTIONS); + this.warehouseID = msg.warehouseID; + this.transactionID = msg.transactionID; + this.warehouse = warehouse; + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.transactionID = reader.getInt(); //some odd type? + this.warehouseID = reader.getInt(); + reader.getInt(); + } + + // Method pre-caches and configures values so they are + // available before we attempt serialization + + public void configure() { + + warehouseBuilding = BuildingManager.getBuilding(this.warehouse.getBuildingUID()); + transactions = new ArrayList<>(50); + + if (this.warehouse.getTransactions().size() > 150) { + transactions.addAll(this.warehouse.getTransactions().subList(this.warehouse.getTransactions().size() - 150, this.warehouse.getTransactions().size())); + } else + transactions = this.warehouse.getTransactions(); + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putInt(this.transactionID); + writer.putInt(this.warehouse.getBuildingUID()); + writer.putInt(transactions.size()); //list Size + + for (Transaction transaction : transactions) { + String name = "No Name"; + switch (transaction.getTargetType()) { + case Building: + Building building = BuildingManager.getBuildingFromCache(transaction.getTargetUUID()); + if (building != null) + name = building.getName(); + Mine mine = Mine.getMineFromTower(transaction.getTargetUUID()); + // + if (mine != null) + name = mine.getZoneName(); + + if (transaction.getTransactionType().equals(TransactionType.TAXRESOURCE) || transaction.getTransactionType().equals(TransactionType.TAXRESOURCEDEPOSIT)) { + City city = building.getCity(); + + if (city != null) + name = city.getCityName(); + } + + break; + case PlayerCharacter: + PlayerCharacter pc = PlayerCharacter.getPlayerCharacter(transaction.getTargetUUID()); + if (pc != null) + name = pc.getCombinedName(); + break; + case NPC: + NPC npc = NPC.getFromCache(transaction.getTargetUUID()); + if (npc != null) { + + if (npc.getBuilding() != null) + name = npc.getBuilding().getName(); + else + name = npc.getName(); + } + + + default: + break; + } + writer.putInt(transaction.getTargetType().ordinal()); //Type + writer.putInt(transaction.getTargetUUID()); //ID + writer.putString(name); //Name of depositer/withdrawler or mine name + writer.putInt(GameObjectType.Building.ordinal()); //Type + writer.putInt(warehouse.getBuildingUID()); //ID + writer.putString(warehouseBuilding.getName()); //warehouse + writer.putInt(transaction.getTransactionType().getID()); //79,80 withdrew, 81 mine produced, 82 deposit + writer.putInt(transaction.getAmount()); //amount + writer.putString(transaction.getResource().name().toLowerCase()); //item type + writer.putDateTime(transaction.getDate()); + } + + + //writer.putString("balls"); + } + + @Override + protected int getPowerOfTwoBufferSize() { + return (16); // 2^14 == 16384 + } + + public int getWarehouseID() { + return warehouseID; + } + + public int getTransactionID() { + return transactionID; + } +} diff --git a/src/engine/net/client/msg/AssetSupportMsg.java b/src/engine/net/client/msg/AssetSupportMsg.java new file mode 100644 index 00000000..540c1464 --- /dev/null +++ b/src/engine/net/client/msg/AssetSupportMsg.java @@ -0,0 +1,333 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.Enum; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import org.pmw.tinylog.Logger; + + +public class AssetSupportMsg extends ClientNetMsg { + + public static int confirmProtect; + private int npcType; + private int npcID; + private int buildingType; + private int buildingID; + private int messageType; + private int pad = 0; + private int objectType; + private int objectUUID; + private int protectedBuildingType; + private int protectedBuildingID; + private int profitTax; + private int weeklyTax; + private byte enforceKOS; + private Enum.SupportMsgType supportMsgType; + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AssetSupportMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ASSETSUPPORT, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + this.messageType = reader.getInt(); + this.supportMsgType = Enum.SupportMsgType.typeLookup.get(this.messageType); + + if (this.supportMsgType == null) { + this.supportMsgType = Enum.SupportMsgType.NONE; + Logger.error("No enumeration for support type" + this.messageType); + } + + switch (supportMsgType) { + + case PROTECT: + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + reader.getInt(); + reader.getInt(); + this.protectedBuildingType = reader.getInt(); + this.protectedBuildingID = reader.getInt(); + reader.getInt(); + this.weeklyTax = reader.getInt(); + this.profitTax = reader.getInt(); + this.enforceKOS = reader.get(); + reader.get(); + reader.getInt(); + reader.getInt(); + break; + + case UNPROTECT: + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.protectedBuildingType = reader.getInt(); + this.protectedBuildingID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + break; + case VIEWUNPROTECTED: + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + break; + case REMOVETAX: + reader.getInt(); + this.buildingID = reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + reader.get(); + reader.get(); + reader.getInt(); + reader.getInt(); + break; + + case ACCEPTTAX: + reader.getInt(); + this.buildingID = reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.get(); + reader.get(); + reader.get(); + break; + } + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putInt(this.messageType); + this.supportMsgType = Enum.SupportMsgType.typeLookup.get(this.messageType); + + if (this.supportMsgType == null) { + this.supportMsgType = Enum.SupportMsgType.NONE; + Logger.error("No enumeration for support type" + this.messageType); + } + + switch (this.supportMsgType) { + + case PROTECT: + writer.putInt(this.buildingType); + writer.putInt(this.buildingID); + writer.putInt(npcType); + writer.putInt(npcID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(this.protectedBuildingType); + writer.putInt(this.protectedBuildingID); + writer.putInt(0); + writer.putInt(this.weeklyTax); + writer.putInt(this.profitTax); + writer.put(this.enforceKOS); + writer.put((byte) 0); + writer.putInt(0); + writer.putInt(0); + break; + case CONFIRMPROTECT: + writer.putInt(this.buildingType); + writer.putInt(this.buildingID); + writer.putInt(this.npcType); + writer.putInt(this.npcID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + + + break; + case UNPROTECT: + writer.putInt(this.buildingType); + writer.putInt(this.buildingID); + writer.putInt(npcType); + writer.putInt(npcID); + writer.putInt(this.protectedBuildingType); + writer.putInt(this.protectedBuildingID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + break; + case VIEWUNPROTECTED: + writer.putInt(this.buildingType); + writer.putInt(this.buildingID); + writer.putInt(npcType); + writer.putInt(npcID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + break; + } + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getUUID() { + return objectUUID; + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getMessageType() { + return messageType; + } + + public void setMessageType(int messageType) { + this.messageType = messageType; + } + + + public int getNpcType() { + return npcType; + } + + public void setNpcType(int npcType) { + this.npcType = npcType; + } + + public int getNpcID() { + return npcID; + } + + public void setNpcID(int npcID) { + this.npcID = npcID; + } + + public int getBuildingType() { + return buildingType; + } + + public void setBuildingType(int buildingType) { + this.buildingType = buildingType; + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public int getProtectedBuildingType() { + return protectedBuildingType; + } + + public void setProtectedBuildingType(int protectedBuildingType) { + this.protectedBuildingType = protectedBuildingType; + } + + public int getProtectedBuildingID() { + return protectedBuildingID; + } + + public void setProtectedBuildingID(int protectedBuildingID) { + this.protectedBuildingID = protectedBuildingID; + } + + public int getWeeklyTax() { + return weeklyTax; + } + + public void setWeeklyTax(int weeklyTax) { + this.weeklyTax = weeklyTax; + } + + public int getProfitTax() { + return profitTax; + } + + public void setProfitTax(int profitTax) { + this.profitTax = profitTax; + } + + public byte getEnforceKOS() { + return enforceKOS; + } + + public void setEnforceKOS(byte enforceKOS) { + this.enforceKOS = enforceKOS; + } +} diff --git a/src/engine/net/client/msg/AttackCmdMsg.java b/src/engine/net/client/msg/AttackCmdMsg.java new file mode 100644 index 00000000..f2c41b4e --- /dev/null +++ b/src/engine/net/client/msg/AttackCmdMsg.java @@ -0,0 +1,113 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class AttackCmdMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private int targetType; + private int targetID; + + /** + * This is the general purpose constructor. + */ + public AttackCmdMsg(int sourceType, int sourceID, int targetType, int targetID) { + super(Protocol.REQUESTMELEEATTACK); + this.sourceType = sourceType; + this.sourceID = sourceID; + this.targetType = targetType; + this.targetID = targetID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AttackCmdMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTMELEEATTACK, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + +} diff --git a/src/engine/net/client/msg/BuyFromNPCMsg.java b/src/engine/net/client/msg/BuyFromNPCMsg.java new file mode 100644 index 00000000..748ab1f8 --- /dev/null +++ b/src/engine/net/client/msg/BuyFromNPCMsg.java @@ -0,0 +1,138 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Item; + +public class BuyFromNPCMsg extends ClientNetMsg { + + private int npcType; + private int npcID; + private int itemType; + private int itemID; + private byte unknown01; + private Item item; + + /** + * This is the general purpose constructor + */ + public BuyFromNPCMsg() { + super(Protocol.BUYFROMNPC); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public BuyFromNPCMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.BUYFROMNPC, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.itemType = reader.getInt(); + this.itemID = reader.getInt(); + this.unknown01 = reader.get(); + reader.get(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + + writer.putInt(this.npcType); + writer.putInt(this.npcID); + + if (this.item != null) { + writer.putInt(this.item.getObjectType().ordinal()); + writer.putInt(this.item.getObjectUUID()); + } else { + writer.putInt(this.itemType); + writer.putInt(this.itemID); + } + + writer.put(this.unknown01); + if (item != null) { + writer.put((byte) 1); + Item.serializeForClientMsgWithoutSlot(item, writer); + } else { + writer.put((byte) 0); + } + } + + @Override + protected int getPowerOfTwoBufferSize() { + return (16); // 2^14 == 16384 + } + + public int getNPCType() { + return this.npcType; + } + + public void setNPCType(int value) { + this.npcType = value; + } + + public int getNPCID() { + return this.npcID; + } + + public void setNPCID(int value) { + this.npcID = value; + } + + public int getItemType() { + return this.itemType; + } + + public void setItemType(int value) { + this.itemType = value; + } + + public int getItemID() { + return this.itemID; + } + + public void setItemID(int value) { + this.itemID = value; + } + + public byte getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(byte value) { + this.unknown01 = value; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } +} diff --git a/src/engine/net/client/msg/BuyFromNPCWindowMsg.java b/src/engine/net/client/msg/BuyFromNPCWindowMsg.java new file mode 100644 index 00000000..673d22d8 --- /dev/null +++ b/src/engine/net/client/msg/BuyFromNPCWindowMsg.java @@ -0,0 +1,260 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.ItemType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.*; + +import java.util.ArrayList; + +public class BuyFromNPCWindowMsg extends ClientNetMsg { + + private int unknown01; + private int npcType; + private int npcID; + private float unknown02; + private byte unknown03; + private int unknown04; + + /** + * This is the general purpose constructor + */ + public BuyFromNPCWindowMsg(int unknown01, int npcType, int npcID, + float unknown02, byte unknown03, int unknown04) { + super(Protocol.SHOPLIST); + this.unknown01 = unknown01; + this.npcType = npcType; + this.npcID = npcID; + this.unknown02 = unknown02; + this.unknown03 = unknown03; + this.unknown04 = unknown04; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public BuyFromNPCWindowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SHOPLIST, origin, reader); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (16); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + npcType = reader.getInt(); + npcID = reader.getInt(); + unknown02 = reader.getFloat(); + unknown03 = reader.get(); + unknown04 = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + + ClientConnection clientConnection = (ClientConnection) this.getOrigin(); + PlayerCharacter player = null; + + if (clientConnection != null) + player = clientConnection.getPlayerCharacter(); + + float sellPercent = 1; + + NPC npc = NPC.getFromCache(npcID); + CharacterItemManager man = null; + ArrayList inventory = null; + ArrayList sellInventory = null; + + if (npc != null) { + man = npc.getCharItemManager(); + Contract contract = npc.getContract(); + if (player != null) { + float barget = player.getBargain(); + float profit = npc.getSellPercent(player) - barget; + + if (profit < 1) + profit = 1; + + sellPercent = 1 * profit; + } else + sellPercent = 1 * npc.getSellPercent(); + + if (contract != null) + sellInventory = contract.getSellInventory(); + } + + if (man != null) + inventory = man.getInventory(); + + writer.putInt(unknown01); + writer.putInt(npcType); + writer.putInt(npcID); + + writer.putFloat(sellPercent); //npc sell markup + + int size = 0; + + if (inventory != null) + size += inventory.size(); + + if (sellInventory != null) + size += sellInventory.size(); + + if (size == 0) { + writer.put((byte) 0); + writer.putInt(0); + return; + } + + writer.put((byte) 1); + + int ownerID = npc.getObjectUUID(); + int indexPosition = writer.position(); + writer.putInt(0); //placeholder for item cnt + int total = 0; + + //add generic sell inventory from contract + if (sellInventory != null) { + + for (MobEquipment mobEquipment : sellInventory) { + try { + MobEquipment.serializeForVendor(mobEquipment, writer, sellPercent); + } catch (SerializationException se) { + continue; + } + ++total; + } + } + + //add npc inventory + if (inventory != null) { + for (Item item : inventory) { + if (item.getOwnerID() != ownerID) + continue; + if (item.getItemBase().getType().equals(ItemType.GOLD)) { + if (item.getNumOfItems() == 0) + continue; + } + Item.serializeForClientMsgForVendorWithoutSlot(item, writer, sellPercent); + ++total; + } + } + writer.putIntAt(total, indexPosition); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public float getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(float unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public byte getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(byte unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the npcType + */ + public int getNpcType() { + return npcType; + } + + /** + * @param npcType the npcType to set + */ + public void setNpcType(int npcType) { + this.npcType = npcType; + } + + /** + * @return the npcID + */ + public int getNpcID() { + return npcID; + } + + /** + * @param npcID the npcID to set + */ + public void setNpcID(int npcID) { + this.npcID = npcID; + } + +} diff --git a/src/engine/net/client/msg/ChangeAltitudeMsg.java b/src/engine/net/client/msg/ChangeAltitudeMsg.java new file mode 100644 index 00000000..924a7031 --- /dev/null +++ b/src/engine/net/client/msg/ChangeAltitudeMsg.java @@ -0,0 +1,146 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + +public class ChangeAltitudeMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private boolean up; + private float startAlt; + private float targetAlt; + private float amountToMove; + private byte unknown01 = (byte) 0; + + /** + * This is the general purpose constructor. + */ + public ChangeAltitudeMsg() { + super(Protocol.CHANGEALTITUDE); + } + + public ChangeAltitudeMsg(int sourceType, int sourceID, boolean up, float startAlt, float targetAlt, float amountToMove) { + super(Protocol.CHANGEALTITUDE); + this.sourceType = sourceType; + this.sourceID = sourceID; + this.startAlt = startAlt; + this.targetAlt = targetAlt; + this.amountToMove = amountToMove; + this.up = up; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChangeAltitudeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHANGEALTITUDE, origin, reader); + } + + public static ChangeAltitudeMsg GroundPlayerMsg(PlayerCharacter pc) { + + ChangeAltitudeMsg msg = new ChangeAltitudeMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), false, pc.getAltitude(), 0, pc.getAltitude()); + return msg; + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.put((this.up ? (byte) 1 : (byte) 0)); + writer.putFloat(this.startAlt); + writer.putFloat(this.targetAlt); + writer.putFloat(this.amountToMove); + writer.put((byte) 0); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.up = (reader.get() == (byte) 1); + this.startAlt = reader.getFloat(); + this.targetAlt = reader.getFloat(); + this.amountToMove = reader.getFloat(); + this.unknown01 = reader.get(); + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public boolean up() { + return this.up; + } + + public float getStartAlt() { + return this.startAlt; + } + + public void setStartAlt(float value) { + this.startAlt = value; + } + + public float getTargetAlt() { + return this.targetAlt; + } + + public void setTargetAlt(float value) { + this.targetAlt = value; + } + + public float getAmountToMove() { + return this.amountToMove; + } + + public void setAmountToMove(float value) { + this.amountToMove = value; + } + + public byte getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(byte value) { + this.unknown01 = value; + } + + public void setUp(boolean value) { + this.up = value; + } +} diff --git a/src/engine/net/client/msg/ChangeGuildLeaderMsg.java b/src/engine/net/client/msg/ChangeGuildLeaderMsg.java new file mode 100644 index 00000000..38423144 --- /dev/null +++ b/src/engine/net/client/msg/ChangeGuildLeaderMsg.java @@ -0,0 +1,72 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ChangeGuildLeaderMsg extends ClientNetMsg { + + private int targetID; + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChangeGuildLeaderMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHANGEGUILDLEADER, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + targetID = reader.getInt(); + reader.getInt(); + reader.get(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(targetID); + writer.put((byte) 100); + writer.putInt(0); + + + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } +} diff --git a/src/engine/net/client/msg/ChatFilterMsg.java b/src/engine/net/client/msg/ChatFilterMsg.java new file mode 100644 index 00000000..cd3e8bcd --- /dev/null +++ b/src/engine/net/client/msg/ChatFilterMsg.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ChatFilterMsg extends ClientNetMsg { + + private int channel; + private byte mute; + + /** + * This is the general purpose constructor. + * + * @param channel + */ + public ChatFilterMsg(int channel) { + super(Protocol.CHANNELMUTE); + this.channel = channel; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatFilterMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHANNELMUTE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.channel); + writer.put(this.mute); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.channel = reader.getInt(); + this.mute = reader.get(); + } + + public int getChannel() { + return this.channel; + } + + public byte getMute() { + return mute; + } + + public void setMute(byte mute) { + this.mute = mute; + } +} diff --git a/src/engine/net/client/msg/CityAssetMsg.java b/src/engine/net/client/msg/CityAssetMsg.java new file mode 100644 index 00000000..e83a1830 --- /dev/null +++ b/src/engine/net/client/msg/CityAssetMsg.java @@ -0,0 +1,218 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.ProtectionState; +import engine.exception.SerializationException; +import engine.gameManager.BuildingManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.Zone; +import org.pmw.tinylog.Logger; + +import java.util.HashSet; +import java.util.Set; + +public class CityAssetMsg extends ClientNetMsg { + + Set allCityAssets; + Set canProtectAssets; + private int type; + private int buildingID; + private int size; + private int pad = 0; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public CityAssetMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CITYASSET, origin, reader); + } + + public CityAssetMsg() { + super(Protocol.CITYASSET); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + reader.getInt(); + this.buildingID = reader.getInt(); + reader.getInt(); + + } + + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (12); + } + + + // Precache and configure this message before we serialize it + + public void configure() { + + Building tol; + Zone zone; + City city; + + canProtectAssets = new HashSet<>(); + + tol = BuildingManager.getBuildingFromCache(this.buildingID); + + if (tol == null) { + Logger.debug("TOL is null"); + return; + } + + zone = tol.getParentZone(); + + if (zone == null) { + Logger.debug("Zone is null"); + return; + } + + city = City.getCity(zone.getPlayerCityUUID()); + + if (city == null) { + Logger.debug("Failed to load city data for Tree of life."); + return; + } + + allCityAssets = zone.zoneBuildingSet; + + for (Building building : allCityAssets) { + + if (building.getBlueprint() == null) + continue; + + // Protected assets do not show up on list + + if (building.assetIsProtected() == true) + continue; + + if (building.getProtectionState() == ProtectionState.PENDING) + continue; + + // Shouldn't need this but just in case + + if (building.getBlueprint().isWallPiece()) + continue; + + if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.TOL)) + continue; + + if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.BANESTONE)) + continue; + + if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.SHRINE)) + continue; + + if (!city.isLocationOnCityGrid(building.getLoc())) + continue; + + canProtectAssets.add(building); + + } + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + String buildingName; + + writer.putInt(0); + writer.putInt(0); + + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + + writer.putInt(canProtectAssets.size()); + + for (Building cityBuilding : canProtectAssets) { + + buildingName = cityBuilding.getName(); + + if (buildingName.isEmpty() && cityBuilding.getBlueprint() != null) { + buildingName = cityBuilding.getBlueprint().getName(); + } + + writer.putInt(cityBuilding.getObjectType().ordinal()); + writer.putInt(cityBuilding.getObjectUUID()); + + writer.putString(buildingName); + writer.putString(cityBuilding.getGuild().getName()); + writer.putInt(20);// \/ Temp \/ + writer.putInt(cityBuilding.getRank()); + + if (cityBuilding.getBlueprint() != null) { + writer.putInt(cityBuilding.getBlueprint().getIcon()); + } else { + writer.putInt(0); + } + writer.putInt(7); //TODO identify these Guild tags?? + writer.putInt(17); + writer.putInt(14); + writer.putInt(14); + writer.putInt(98);// /\ Temp /\ + + } + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + +} diff --git a/src/engine/net/client/msg/CityChoiceMsg.java b/src/engine/net/client/msg/CityChoiceMsg.java new file mode 100644 index 00000000..1e89ec07 --- /dev/null +++ b/src/engine/net/client/msg/CityChoiceMsg.java @@ -0,0 +1,108 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class CityChoiceMsg extends ClientNetMsg { + + private PlayerCharacter pc; + private boolean isTeleport; + private int msgType; + private int cityID; + + /** + * This is the general purpose constructor. + */ + public CityChoiceMsg(PlayerCharacter pc, boolean isTeleport) { + super(Protocol.CITYCHOICE); + this.pc = pc; + this.isTeleport = isTeleport; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public CityChoiceMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CITYCHOICE, origin, reader); + } + + /** + * Copy constructor + */ + public CityChoiceMsg(CityChoiceMsg msg) { + super(Protocol.CITYCHOICE); + this.pc = msg.pc; + this.isTeleport = msg.isTeleport; + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (12); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //Do we even want to try this? + this.msgType = reader.getInt(); + reader.getInt(); + this.cityID = reader.getInt(); + reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + public PlayerCharacter pc() { + return this.pc; + } + + public boolean isTeleport() { + return this.isTeleport; + } + + public void setPC(PlayerCharacter pc) { + this.pc = pc; + } + + public void setIsTeleport(boolean value) { + this.isTeleport = value; + } + + public int getMsgType() { + return msgType; + } + + public int getCityID() { + return cityID; + } +} diff --git a/src/engine/net/client/msg/CityDataMsg.java b/src/engine/net/client/msg/CityDataMsg.java new file mode 100644 index 00000000..f5f679e7 --- /dev/null +++ b/src/engine/net/client/msg/CityDataMsg.java @@ -0,0 +1,272 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.Network; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; +import engine.objects.City; +import engine.objects.Mine; +import engine.objects.Runegate; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class CityDataMsg extends ClientNetMsg { + + public static final long wdComp = 0xFF00FF0000000003L; + private static final byte ver = 1; + private static ByteBuffer cachedEnterWorld; + private static long cachedExpireTime; + private final boolean forEnterWorld; + private Session s; + private boolean updateCities = false; + private boolean updateRunegates = false; + private boolean updateMines = false; + + /** + * This is the general purpose constructor. + * + * @param s Session + * @param forEnterWorld boolean flag + */ + public CityDataMsg(Session s, boolean forEnterWorld) { + super(Protocol.CITYDATA); + this.s = s; + this.forEnterWorld = forEnterWorld; + } + + public CityDataMsg(boolean updateCities, boolean updateRunegates, boolean updateMines) { + super(Protocol.CITYDATA); + this.s = null; + this.forEnterWorld = false; + this.updateCities = updateCities; + this.updateRunegates = updateRunegates; + this.updateMines = updateMines; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public CityDataMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CITYDATA, origin, reader); + this.forEnterWorld = false; + } + + private static void attemptSerializeForEnterWorld(ByteBuffer bb) { + bb.clear(); + ByteBufferWriter temp = new ByteBufferWriter(bb); + temp.put((byte) 0); // PAD + + + ArrayList cityList = new ArrayList<>(); + ConcurrentHashMap map = DbManager.getMap(Enum.GameObjectType.City); + for (AbstractGameObject ago : map.values()) + + if (ago.getObjectType().equals(Enum.GameObjectType.City)) + cityList.add((City) ago); + + temp.putInt(cityList.size()); + + for (City city : cityList) + City.serializeForClientMsg(city, temp); + temp.put((byte) 0); // PAD + + // Serialize runegates + + temp.putInt(Runegate._runegates.values().size()); + + for (Runegate runegate : Runegate._runegates.values()) { + runegate._serializeForEnterWorld(temp); + } + + ArrayList mineList = new ArrayList<>(); + for (Mine toAdd : Mine.mineMap.keySet()) { + mineList.add(toAdd); + } + + temp.putInt(mineList.size()); + for (Mine mine : mineList) + Mine.serializeForClientMsg(mine, temp); + temp.put((byte) 0); // PAD + } + + @Override + protected int getPowerOfTwoBufferSize() { + return (18); // 2^14 == 16384 + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + if (this.forEnterWorld) + serializeForEnterWorld(writer); + else + serializeForMapUpdate(writer); + } + + /** + * Specific use serializer + * + * @param writer + */ + private void serializeForMapUpdate(ByteBufferWriter writer) { + + //Handle City updates + + if (this.updateCities) { + writer.put((byte) 0); + ArrayList cityList = new ArrayList<>(); + ConcurrentHashMap map = DbManager.getMap(Enum.GameObjectType.City); + if (map != null) { + for (AbstractGameObject ago : map.values()) + if (ago.getObjectType().equals(Enum.GameObjectType.City)) + cityList.add((City) ago); + + writer.putInt(cityList.size()); + for (City city : cityList) { + City.serializeForClientMsg(city, writer); + } + + } else { + Logger.error("missing city map"); + writer.putInt(0); + } + } else + writer.put((byte) 1); + + + //Handle Runegate updates + if (this.updateRunegates) { + + writer.put((byte) 0); + writer.putInt(Runegate._runegates.values().size()); + + for (Runegate runegate : Runegate._runegates.values()) { + runegate._serializeForEnterWorld(writer); + } + } else + writer.put((byte) 1); + + + //Handle Mine updates + try { + if (this.updateMines) { + ArrayList mineList = new ArrayList<>(); + for (Mine toAdd : Mine.mineMap.keySet()) { + mineList.add(toAdd); + } + + writer.putInt(mineList.size()); + for (Mine mine : mineList) + Mine.serializeForClientMsg(mine, writer); + } else + writer.putInt(0); + } catch (Exception e) { + Logger.error(e); + } + + + writer.put((byte) 0); // PAD + } + + /** + * Specific use serializer + * + * @param writer + */ + private void serializeForEnterWorld(ByteBufferWriter writer) { + if (s == null || s.getPlayerCharacter() == null) + return; + + long startT = System.currentTimeMillis(); + + if (cachedEnterWorld == null) { + // Never before been cached, so init stuff + cachedEnterWorld = Network.byteBufferPool.getBuffer(19); + cachedExpireTime = 0L; + } + + //Check to see if its time to renew cache. + if (cachedExpireTime < System.currentTimeMillis()) { + synchronized (cachedEnterWorld) { + CityDataMsg.attemptSerializeForEnterWorld(cachedEnterWorld); + } + cachedExpireTime = startT + 60000; + } + + writer.putBB(cachedEnterWorld); + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + // Client only sends 11 bytes. + + byte type = reader.get(); + + if (type == 1) { + reader.get(); + reader.get(); + reader.getInt(); + + } else { + reader.get(); + reader.getInt(); + reader.get(); + reader.getInt(); + } + + } + + /** + * @return the s + */ + public Session getS() { + return s; + } + + /** + * @return the forEnterWorld + */ + public boolean isForEnterWorld() { + return forEnterWorld; + } + + public void updateCities(boolean value) { + this.updateCities = value; + } + + public void updateRunegates(boolean value) { + this.updateRunegates = value; + } + + public void updateMines(boolean value) { + this.updateMines = value; + } + + +} diff --git a/src/engine/net/client/msg/CityZoneMsg.java b/src/engine/net/client/msg/CityZoneMsg.java new file mode 100644 index 00000000..80736b50 --- /dev/null +++ b/src/engine/net/client/msg/CityZoneMsg.java @@ -0,0 +1,154 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Zone; + +public class CityZoneMsg extends ClientNetMsg { + + private int messageType; //1 or 2 + private int zoneType; + private int zoneID; + private float locX; + private float locY; + private float locZ; + private String name; + private float radiusX; + private float radiusZ; + private int unknown01 = 0; + + /** + * This is the general purpose constructor. + */ + + public CityZoneMsg(int messageType, float locX, float locY, float locZ, String name, Zone zone, float radiusX, float radiusZ) { + super(Protocol.CITYZONE); + this.messageType = messageType; //only 1 or 2 used on message type + this.zoneType = GameObjectType.Zone.ordinal(); + this.zoneID = zone.getObjectUUID(); + this.locX = locX; + this.locY = locY; + this.locZ = locZ; + this.name = name; + this.radiusX = radiusX; + this.radiusZ = radiusZ; + this.unknown01 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public CityZoneMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CITYZONE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + + writer.putInt(this.messageType); + + + //MSGTYPE 3 SERIALIZATION + if (this.messageType == 3) { + writer.putInt(1); + writer.putInt(this.zoneType); + writer.putInt(this.zoneID); + writer.putString("RuinedCity"); + writer.putString("Ruined"); + writer.putFloat(this.locX); + writer.putFloat(this.locY); + writer.putFloat(this.locZ); + writer.putInt(0); + return; + } + + + //END SERIALIZIONTYPE 3 + + // writer.putInt(this.messageType); + if (this.messageType == 1) { + writer.putInt(this.zoneType); + writer.putInt(this.zoneID); + } + + writer.putFloat(this.locX); + writer.putFloat(this.locY); + writer.putFloat(this.locZ); + writer.putString(this.name); + if (this.messageType == 1) { + writer.putFloat(this.radiusX); + writer.putFloat(this.radiusZ); + } + writer.putInt(this.unknown01); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + // this.locX = reader.getFloat(); + // this.locY = reader.getFloat(); + // this.locZ = reader.getFloat(); + // this.name = reader.getString(); + // this.unknown01 = reader.getInt(); + } + + public float getLocX() { + return this.locX; + } + + public void setLocX(float value) { + this.locX = value; + } + + public float getLocY() { + return this.locY; + } + + public void setLocY(float value) { + this.locY = value; + } + + public float getLocZ() { + return this.locZ; + } + + public void setLocZ(float value) { + this.locZ = value; + } + + public String getName() { + return this.name; + } + + public void setName(String value) { + this.name = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + +} diff --git a/src/engine/net/client/msg/ClaimAssetMsg.java b/src/engine/net/client/msg/ClaimAssetMsg.java new file mode 100644 index 00000000..5e41afae --- /dev/null +++ b/src/engine/net/client/msg/ClaimAssetMsg.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ClaimAssetMsg extends ClientNetMsg { + private int pad; + private int objectType; + private int objectUUID; + + public ClaimAssetMsg(int objectType, int objectUUID) { + super(Protocol.CLAIMASSET); + this.pad = 0; + this.objectType = objectType; + this.objectUUID = objectUUID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ClaimAssetMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CLAIMASSET, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.pad = reader.getInt(); + this.objectType = reader.getInt(); + this.objectUUID = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.pad); + writer.putInt(this.objectType); + writer.putInt(this.objectUUID); + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getUUID() { + return objectUUID; + } +} diff --git a/src/engine/net/client/msg/ClaimGuildTreeMsg.java b/src/engine/net/client/msg/ClaimGuildTreeMsg.java new file mode 100644 index 00000000..de4d2767 --- /dev/null +++ b/src/engine/net/client/msg/ClaimGuildTreeMsg.java @@ -0,0 +1,383 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +/** + * Open manage city asset window + * + * @author Eighty + */ +public class ClaimGuildTreeMsg extends ClientNetMsg { + + // 2 = manage this asset. 20 = manage entire city + + public static final int RENAME_TREE = 2; + public static final int OPEN_CITY = 4; + public static final int CLOSE_CITY = 5; + private int messageType; + private int targetType; + private int targetID; + private int charter; + private int bgc1; + private int bgc2; + private int symbolColor; + private int bgDesign; + private int symbol; + private int unknown07; + private int unknown08; + private int unknown09; + private int unknown10; + private int unknown11; + private String CityName; + private String OwnerName; + private String GuildName; + private int unknown12; + private byte UnkByte01; + private int unknown13; + private int unknown14; + private int unknown15; + private int unknown16; + private int unknown17; + private int unknown18; + private byte UnkByte02; + private byte UnkByte03; + private byte UnkByte04; + private byte UnkByte05; + private int unknown19; //Arraylist motto length? + private String motto; //motto Length 60 max? + private String treeName; + +// private int unknown01; + + + /** + * This is the general purpose constructor + */ + public ClaimGuildTreeMsg() { + super(Protocol.CLAIMGUILDTREE); + this.messageType = 0; + this.targetType = 0; + this.targetID = 0; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ClaimGuildTreeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CLAIMGUILDTREE, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.messageType = reader.getInt(); + switch (this.messageType) { + case OPEN_CITY: + case CLOSE_CITY: + targetType = reader.getInt(); + targetID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + break; + case RENAME_TREE: + targetType = reader.getInt(); + targetID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.treeName = reader.getString(); + break; + default: + targetType = reader.getInt(); + targetID = reader.getInt(); + reader.getInt(); + reader.getInt(); + this.treeName = reader.getString(); + break; + } + + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.messageType); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + if (this.messageType == RENAME_TREE) + writer.putString(this.treeName); + } + + /** + * @return the charter + */ + public int getcharter() { + return charter; + } + + + public int getbgc1() { + return bgc1; + } + + public int getbgc2() { + return bgc2; + } + + public int getsymbolColor() { + return symbolColor; + } + + public int getbgDesign() { + return bgDesign; + } + + public int getsymbol() { + return symbol; + } + + public int getUnknown07() { + return unknown07; + } + + public void setUnknown07(int unknown07) { + this.unknown07 = unknown07; + } + + public int getUnknown08() { + return unknown08; + } + + public void setUnknown08(int unknown08) { + this.unknown08 = unknown08; + } + + public int getUnknown09() { + return unknown09; + } + + public void setUnknown09(int unknown09) { + this.unknown09 = unknown09; + } + + public int getUnknown10() { + return unknown10; + } + + public void setUnknown10(int unknown10) { + this.unknown10 = unknown10; + } + + public int getUnknown11() { + return unknown11; + } + + public void setUnknown11(int unknown11) { + this.unknown11 = unknown11; + } + + public int getUnknown12() { + return unknown12; + } + + public void setUnknown12(int unknown12) { + this.unknown12 = unknown12; + } + + public int getUnknown13() { + return unknown13; + } + + public void setUnknown13(int unknown13) { + this.unknown13 = unknown13; + } + + public int getUnknown14() { + return unknown14; + } + + public void setUnknown14(int unknown14) { + this.unknown14 = unknown14; + } + + public int getUnknown15() { + return unknown15; + } + + public void setUnknown15(int unknown15) { + this.unknown15 = unknown15; + } + + public int getUnknown16() { + return unknown16; + } + + public void setUnknown16(int unknown16) { + this.unknown16 = unknown16; + } + + public int getUnknown17() { + return unknown17; + } + + public void setUnknown17(int unknown17) { + this.unknown17 = unknown17; + } + + public int getUnknown18() { + return unknown18; + } + + public void setUnknown18(int unknown18) { + this.unknown18 = unknown18; + } + + public int getUnknown19() { + return unknown19; + } + + public void setUnknown19(int unknown19) { + this.unknown19 = unknown19; + } + + public String getOwnerName() { + return OwnerName; + } + + public void setOwnerName(String OwnerName) { + this.OwnerName = OwnerName; + } + + public String getCityName() { + return CityName; + } + + public void setCityName(String CityName) { + this.CityName = CityName; + } + + public String getGuildName() { + return GuildName; + } + + public void setGuildName(String GuildName) { + this.GuildName = GuildName; + } + + public void setcharter(int charter) { + this.charter = charter; + } + + public void setbgc1(int bgc1) { + this.bgc1 = bgc1; + } + + public void setbgc2(int bgc2) { + this.bgc2 = bgc2; + } + + public void setsymbolColor(int symbolColor) { + this.symbolColor = symbolColor; + } + + public void setbgDesign(int bgDesign) { + this.bgDesign = bgDesign; + } + + public void setsymbol(int symbol) { + this.symbol = symbol; + } + + public void setUnkByte01(byte UnkByte01) { + this.UnkByte01 = UnkByte01; + } + + public void setUnkByte02(byte UnkByte02) { + this.UnkByte02 = UnkByte02; + } + + public void setUnkByte03(byte UnkByte03) { + this.UnkByte03 = UnkByte03; + } + + public void setUnkByte04(byte UnkByte04) { + this.UnkByte04 = UnkByte04; + } + + public String getMotto() { + return motto; + } + + public void setMotto(String motto) { + this.motto = motto; + } + + public byte getUnkByte05() { + return UnkByte05; + } + + public void setUnkByte05(byte unkByte05) { + UnkByte05 = unkByte05; + } + + public int getMessageType() { + return messageType; + } + + public void setMessageType(int value) { + this.messageType = value; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + + public String getTreeName() { + return treeName; + } + +} + +//Debug Info +//Run: Failed to make object TEMPLATE:135700 INSTANCE:1717987027141... (t=50.46) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027161... (t=50.46) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027177... (t=50.67) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:60040 INSTANCE:1717987027344... (t=50.87) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:3 INSTANCE:1717987027164... (t=50.88) (r=7/4/2011 11:56:39) + diff --git a/src/engine/net/client/msg/ClientNetMsg.java b/src/engine/net/client/msg/ClientNetMsg.java new file mode 100644 index 00000000..250d810c --- /dev/null +++ b/src/engine/net/client/msg/ClientNetMsg.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public abstract class ClientNetMsg extends AbstractNetMsg { + + /** + * This is the general purpose constructor. + */ + protected ClientNetMsg(Protocol protocolMsg) { + super(protocolMsg); + } + + protected ClientNetMsg(Protocol protocolMsg, ClientNetMsg msg) { + super(protocolMsg, msg); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + protected ClientNetMsg(Protocol protocolMsg, AbstractConnection origin, + ByteBufferReader reader) { + super(protocolMsg, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected abstract void _deserialize(ByteBufferReader reader) + ; + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected abstract void _serialize(ByteBufferWriter writer) + throws SerializationException; + + /* + * return the header size of 4 for ClientMsgs + */ + @Override + protected int getHeaderSize() { + return 4; + } + + /* + * Write in the header for ClientMsgs + */ + @Override + protected void writeHeaderAt(int startPos, ByteBufferWriter writer) { + writer.putIntAt(this.getProtocolMsg().opcode, startPos); + } +} diff --git a/src/engine/net/client/msg/CloseTradeWindowMsg.java b/src/engine/net/client/msg/CloseTradeWindowMsg.java new file mode 100644 index 00000000..322d9c4b --- /dev/null +++ b/src/engine/net/client/msg/CloseTradeWindowMsg.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Commit to trade + * + * @author Eighty + */ +public class CloseTradeWindowMsg extends ClientNetMsg { + + private int playerType; + private int playerID; + private int unknown01; + + /** + * This is the general purpose constructor + */ + public CloseTradeWindowMsg(AbstractGameObject player, int unknown01) { + super(Protocol.TRADECLOSE); + this.playerType = player.getObjectType().ordinal(); + this.playerID = player.getObjectUUID(); + this.unknown01 = unknown01; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public CloseTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRADECLOSE, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + playerType = reader.getInt(); + playerID = reader.getInt(); + unknown01 = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(playerType); + writer.putInt(playerID); + + writer.putInt(unknown01); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + +} diff --git a/src/engine/net/client/msg/CommitToTradeMsg.java b/src/engine/net/client/msg/CommitToTradeMsg.java new file mode 100644 index 00000000..f0bd2e71 --- /dev/null +++ b/src/engine/net/client/msg/CommitToTradeMsg.java @@ -0,0 +1,93 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +/** + * Commit to trade + * + * @author Eighty + */ +public class CommitToTradeMsg extends ClientNetMsg { + + private int unknown01; + private long playerCompID; + + /** + * This is the general purpose constructor + */ + public CommitToTradeMsg(int unknown01, long playerCompID) { + super(Protocol.TRADECONFIRM); + this.unknown01 = unknown01; + this.playerCompID = playerCompID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public CommitToTradeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRADECONFIRM, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + playerCompID = reader.getLong(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putLong(playerCompID); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the playerCompID + */ + public long getPlayerCompID() { + return playerCompID; + } + + /** + * @param playerCompID the playerCompID to set + */ + public void setPlayerCompID(long playerCompID) { + this.playerCompID = playerCompID; + } + +} diff --git a/src/engine/net/client/msg/ConfirmPromoteMsg.java b/src/engine/net/client/msg/ConfirmPromoteMsg.java new file mode 100644 index 00000000..0031d0e3 --- /dev/null +++ b/src/engine/net/client/msg/ConfirmPromoteMsg.java @@ -0,0 +1,54 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ConfirmPromoteMsg extends ClientNetMsg { + + + /** + * This is the general purpose constructor. + * + * @param channel + */ + public ConfirmPromoteMsg(int channel) { + super(Protocol.CONFIRMPROMOTE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ConfirmPromoteMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CONFIRMPROMOTE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } +} diff --git a/src/engine/net/client/msg/DeclineFriendMsg.java b/src/engine/net/client/msg/DeclineFriendMsg.java new file mode 100644 index 00000000..d1d52f2c --- /dev/null +++ b/src/engine/net/client/msg/DeclineFriendMsg.java @@ -0,0 +1,64 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class DeclineFriendMsg extends ClientNetMsg { + + public String sourceName; + public String friendName; + + /** + * This is the general purpose constructor. + */ + public DeclineFriendMsg(PlayerCharacter pc) { + super(Protocol.FRIENDDECLINE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public DeclineFriendMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.FRIENDDECLINE, origin, reader); + } + + /** + * Copy constructor + */ + public DeclineFriendMsg(DeclineFriendMsg msg) { + super(Protocol.FRIENDDECLINE); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //Do we even want to try this? + this.sourceName = reader.getString(); //This is source name.. this friends list must never been updated since launch, not using IDS. + this.friendName = reader.getString(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.sourceName); + writer.putString(this.friendName); + } +} diff --git a/src/engine/net/client/msg/DeleteItemMsg.java b/src/engine/net/client/msg/DeleteItemMsg.java new file mode 100644 index 00000000..fd6ea26d --- /dev/null +++ b/src/engine/net/client/msg/DeleteItemMsg.java @@ -0,0 +1,79 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class DeleteItemMsg extends ClientNetMsg { + + private int type; + private int objectUUID; + private int unknown1, unknown2; + + public DeleteItemMsg(int type, int objectUUID) { + super(Protocol.DELETEOBJECT); + this.type = type; + this.objectUUID = objectUUID; + this.unknown1 = 0; + this.unknown2 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public DeleteItemMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.DELETEOBJECT, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + this.objectUUID = reader.getInt(); + this.unknown1 = reader.getInt(); + this.unknown2 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.type); + writer.putInt(this.objectUUID); + writer.putInt(this.unknown1); + writer.putInt(this.unknown2); + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @return the objectUUID + */ + public int getUUID() { + return objectUUID; + } + +} diff --git a/src/engine/net/client/msg/DestroyBuildingMsg.java b/src/engine/net/client/msg/DestroyBuildingMsg.java new file mode 100644 index 00000000..d5df7216 --- /dev/null +++ b/src/engine/net/client/msg/DestroyBuildingMsg.java @@ -0,0 +1,74 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class DestroyBuildingMsg extends ClientNetMsg { + private int pad = 0; + private int objectType; + private int objectUUID; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public DestroyBuildingMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.DESTROYBUILDING, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.pad = reader.getInt(); + this.objectType = reader.getInt(); + this.objectUUID = reader.getInt(); + ; + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.pad); + writer.putInt(this.objectType); + writer.putInt(this.objectUUID); + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getUUID() { + return objectUUID; + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } +} diff --git a/src/engine/net/client/msg/DoorTryOpenMsg.java b/src/engine/net/client/msg/DoorTryOpenMsg.java new file mode 100644 index 00000000..17c6116a --- /dev/null +++ b/src/engine/net/client/msg/DoorTryOpenMsg.java @@ -0,0 +1,130 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class DoorTryOpenMsg extends ClientNetMsg { + + private int unk1; + private int doorID; + private int buildingUUID; + private int playerUUID; + private byte toggle; + + /** + * This is the general purpose constructor. + */ + public DoorTryOpenMsg(int doorID, int targetID, int senderID, byte toggle) { + super(Protocol.DOORTRYOPEN); + this.unk1 = 0; + this.doorID = doorID; + this.buildingUUID = targetID; + this.playerUUID = senderID; + this.toggle = toggle; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public DoorTryOpenMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.DOORTRYOPEN, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + if (this.unk1 == 2) { + writer.putInt(2); + for (int i = 0; i < 6; i++) + writer.putInt(0); + writer.put((byte) 0); + } else { + writer.putInt(0); //0 or 2. If 2 all other variables are 0 + writer.putInt(0); + writer.putInt(doorID); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(buildingUUID); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(playerUUID); + writer.put(toggle); + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unk1 = reader.getInt(); + reader.getInt(); + this.doorID = reader.getInt(); + reader.getInt(); // type padding + this.buildingUUID = reader.getInt(); + reader.getInt(); // type padding + this.playerUUID = reader.getInt(); + this.toggle = reader.get(); + } + + /** + * @return the unknown1 + */ + public int getDoorID() { + return doorID; + } + + /** + * @return the buildingUUID + */ + public int getBuildingUUID() { + return buildingUUID; + } + + /** + * @return the playerUUID + */ + public int playerUUID() { + return playerUUID; + } + + /** + * @return the toggle + */ + public byte getToggle() { + return toggle; + } + + public int getUnk1() { + return this.unk1; + } + + public void setUnk1(int value) { + this.unk1 = value; + } +} diff --git a/src/engine/net/client/msg/DropGoldMsg.java b/src/engine/net/client/msg/DropGoldMsg.java new file mode 100644 index 00000000..667916a5 --- /dev/null +++ b/src/engine/net/client/msg/DropGoldMsg.java @@ -0,0 +1,141 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class DropGoldMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int unknown03; + private short unknown04; + private byte unknown05; + + /** + * This is the general purpose constructor. + */ + public DropGoldMsg() { + super(Protocol.DROPGOLD); + this.unknown01 = 0x40A5BDB0; + this.unknown02 = 0x342AA9F0; + this.unknown03 = 0; + this.unknown04 = (short) 0; + this.unknown05 = (byte) 0; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public DropGoldMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.DROPGOLD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); + reader.get(); + + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public short getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(short unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public byte getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(byte unknown05) { + this.unknown05 = unknown05; + } + +} diff --git a/src/engine/net/client/msg/EnterWorldReceivedMsg.java b/src/engine/net/client/msg/EnterWorldReceivedMsg.java new file mode 100644 index 00000000..b300a16a --- /dev/null +++ b/src/engine/net/client/msg/EnterWorldReceivedMsg.java @@ -0,0 +1,58 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class EnterWorldReceivedMsg extends ClientNetMsg { + + /** + * This is the general purpose constructor. + */ + public EnterWorldReceivedMsg() { + super(Protocol.READYTOENTER); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public EnterWorldReceivedMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.READYTOENTER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + return; + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + return; + } + + @Override + protected int getPowerOfTwoBufferSize() { + return 18; //65536 bytes for enter world. + } +} diff --git a/src/engine/net/client/msg/ErrorPopupMsg.java b/src/engine/net/client/msg/ErrorPopupMsg.java new file mode 100644 index 00000000..e56a2536 --- /dev/null +++ b/src/engine/net/client/msg/ErrorPopupMsg.java @@ -0,0 +1,315 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.net.*; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + +public class ErrorPopupMsg extends ClientNetMsg { + + //1: Sorry, but that individual is not a banker + //2: Sorry, but you must be closer to the banker to access your account + //3: Sorry, but you have insufficient funds to access your acount + //4: The shop is closed + //5: You must come closer to shop + //6: You do not have enough money to purchase that + //7: You cannot carry that item + //8: The banker cannot carry that item + //9: You do not have that much gold to drop + //10: That item cannot be dropped + //11: You cannot drop what you do not have + //12: Sorry, but this container is locked + //13: Sorry, but this container is barred + //14: You must come closer to me + //15: You no longer have that item to sell + //16: I won't buy that kind of item + //17: I cannot afford that item + //18: You can't really afford that + //19: This item is gone from inventory + //20: Your resurection has been declined + //21: I cannot carry that weight + //22: You just dropped that item on the ground + //23: This corpse has no experience to return + //24: This player is not in world + //25: This player is not online + //26: You selected an invalid location + //27: You are dead. Try again when you are not so...well, dead + //28: Your target is dead and cannot be summoned + //29: Your summons has been declined + //30: That person cannot carry that item + //31: An unexpected error has occurred and the trade is being canceled + //32: You must choose a promotion class before gaining your next level. Speak to a class trainer + //33: Promotion failed + //34: Come back when you've gained more experience + //35: This hireling failed to buy the item + //36: I don't buy items + //37: I don't sell items + //38: You appear to be in a building normally + //39: There does not appear to be a building where you are + //40: I don't swear guilds + //41: I server no sovereign + //42: Your guild is not errant + //43: Members of your guild are too high in level + //44: You cannot afford this service + //45: Failure to swear guild + //46: Cannot swear under ruins + //47: Your guild is the wrong type to swear to this guild + //48: Hireling could not hire + //49: I do not hire + //50: This pet is gone + //51: You have successfully promoted to a new class + //52: You have successfully added a new discipline + //53: You no longer meet the level requirement to stay in this guild + //54: That item is too advanced + //55: All production slots are taken + //56: That enchantment is too advanced + //57: That formula is too advanced + //58: The formula is beyond the means of this facility + //59: This hireling does not have this formula + //60: Hireling does not possess that item! + //61: Hireling does not work with such items + //62: You may only trade with items in your inventory + //63: You must be within your building to do that + //64: You are too far from the building to do that + //65: I cannot enthrall creatures + //66: I cannot repledge you + //67: I cannot teleport you + //68: You have no valid thralls + //69: You have a thrall that I do not understand + //70: I cannot afford your thrall + //71: There are no cities to which you can repledge + //72: There are no cities to which you can teleport + //73: You are too low level to repledge + //74: You are too low level to teleport + //75: You must leave your current guild before you can repledge + //76: Failure to repledge + //77: Failure to teleport + //78: You do not meet the qualifications to join that city + //79: There are too many furniture items in this asset + //80: Attempting to add furniture to bad location + //81: This deed codes for a bad furniture prop + //82: Object is not an appropriate furniture deed + //83: Unable to find corresponding furniture on asset + //84: The chose game world is not valid <- will kick out of world + //85: The game world is temporarily unavailable <- will kick out of world + //86: The runegate is unnaffected by this power + //87: You are not powerful enough to activate this gate + //88: This runegate is already on + //89: You cannot unbanish this one until the timestamp expires + //90: You cannot trade while either you or the target is invisible + //91: You cannot trade while in combat mode + //92: That person is already engaged in a trade + //93: You must be closer to trade + //94: The trade was successful + //95: The trade was not successful + //96: The trade has failed because one of you would exceed your gold limit + //97: You must be closer to open that + //98: You cannot loot while flying + //99: Your target is not dead + //100: You cannot loot a trainer + //101: You cannot loot a shopkeeper + //102: You cannot loot a banker + //103: You cannot add this individual to the condemn list + //104: You cannot add the owner's guild to the condemn list + //105: You cannot add the owner's nation to the condemn list + //106: Failure to add to the condemn list + //107: Unable to find desired group + //108: Group is at maximum membership + //109: Failed to add item to hireling + //110: Failer to remove item from hireling + //111: This item cannot be removed from inventory + //112: Your account has no characters + //113: Failure to start support + //114: Cannot add another support + //115: This type of asset cannot receive protection + //116: This asset already has protection upon it + //117: Failure to remove support + //118: Failure to complete support + //119: Failure to reject support + //120: This asset is not a banecircle + //121: You are not a CSR who can advance banecircle stage + //122: Failure to advance banecircle stage + //123: You do not have the authority within your guild to modify this banecircle + //124: Banecircle cannot advance once in final stage + //125: Failure to repair Asset + //126: Asset does not require repair + //127: No gold in asset strongbox + //128: Insufficient funds for even one point of repair + //129: You cannot bond where you are killed-on-sight + //130: You cannot join where you are killed-on-sight + //131: You do not meet the level required for this SWORN guild + //132: You are already a member of this guild + //133: Your banishment from this guild has not yet been lifted + //134: Your QUIT status from this guild has not yet expired + //135: Character is considered BANISHED by guild leadership + //136: Your class is not allowed to teleport here + //137: You have no affiliation with this tree + //138: You can never join this type of tree + //139: You do not meet the safehold level requirement + //140: Ruined trees are invalid + //141: Unclaimed trees are invalid + //142: You are the wrong race for this city + //143: You are the wrong class for this city + //144: You are the wrong sex for this city + //145: You are too low level for this city + //146: You do not meet the level requirements for this city + //147: Tree must be rank 5 to open city + //148: Unable to find a matching petition to complete guild creation + //149: Guild name fails profanity check + //150: Guild motto fails profanity check + //151: Guild name is not unique + //152: Guild crest is not unique + //153: Guild crest is reserved + //154: All three crest colors cannot be the same + //155: Please choose another name + //156: You cannot bank and trade at the same time + //157: You must not move or engage in combat for 10 seconds before stuck will work + //158: Your gold has been dropped on the ground + //159: Merchant cannot purchase item without exceeding his reserve + //160: Rune succesfully applied + //161: You cannot apply that rune + //162: You rely too heavily on that rune to remove it + //163: This shrine does not take offerings of that type + //164: This hireling cannot grant boons + //165: This hireling cannot display the leaderboard + //166: There is no more favor in this shrine to loot + //167: There are no more resources in this warehouse to loot + //168: This boon is only for guild members belonging to this shrine + //169: You do not meet the race/class requirements for this boon + //170: This shrine is no longer capable of granting boons + //171: This asset cannot be destroyed during times of war + //172: This shrine has no favor + //173: You must be the leader of a guild to receive a blessing + //174: This siege spire cannot be toggled yet. Please try again later + //175: You cannot teleport into that zone at the moment + //176: Only guild leaders can claim a territory + //177: Your nation has already reached the maximum number of capitals + //178: This territory is already claimed + //179: Only landed guilds may claim a territory + //180: This territory cannot be ruled by anyone + //181: Your tree must be rank 7 before claiming a territory + //182: This realm is in turmoil and cannot be claimed yet + //183: You cannot rule a guild under a different faction then your parent guild + //184: Insufficient gold or resources to upgrade to capital + //185: You must seek the blessing of the three sages before you can rule + //186: Your tree is not inside a territory! + //187: This realm is in turmoil and cannot yet be claimed! + //188: You must have a warehouse to become a capital + //189: You are not the owner of this building + //190: This building cannot be upgraded further + //191: You don't have the required funds + //192: This building is already upgrading + //193: Production denied: This building must be protected to gain access to warehouse resources.. + //194: The operation failed because you reached your gold limit + //195: That player is currently busy completing the last trade. Try again in a few moments + //196: You are currently busy completing the last trade. Try again in a few moments + //197: You cannot join a guild whose nation has a guild that is currently involved in a siege + //198: You cannot repledge while you are involved in a siege + //199: You already have this boon + //200: Your vault cannot contain that item + //201: You can't put that much gold there + //202: You can't carry that much gold + //203: You don't have that much gold to transfer + //204: That item is not in the vault + //205: That item is not in the inventory + //206: This building can hold no more gold + private int message; + private String custom = ""; + + /** + * This is the general purpose constructor. + */ + public ErrorPopupMsg(int message) { + super(Protocol.STANDARDALERT); + this.message = message; + } + + public ErrorPopupMsg(int message, String custom) { + super(Protocol.STANDARDALERT); + this.message = message; + this.custom = custom; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ErrorPopupMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.STANDARDALERT, origin, reader); + } + + /** + * Copy constructor + */ + public ErrorPopupMsg(ErrorPopupMsg msg) { + super(Protocol.STANDARDALERT); + this.message = msg.message; + } + + /** + * @return unknown01 + */ + + // Popup Window with no title and arbitrary text. + // Find an Enum for generic ERROR or way to set perhaps? + public static void sendErrorMsg(PlayerCharacter player, String errorMessage) { + + if (player == null) + return; + + + ErrorPopupMsg popupMessage; + Dispatch errorDispatch; + + popupMessage = new ErrorPopupMsg(300, errorMessage); + + errorDispatch = Dispatch.borrow(player, popupMessage); + DispatchMessage.dispatchMsgDispatch(errorDispatch, Enum.DispatchChannel.SECONDARY); + + } + + public static void sendErrorPopup(PlayerCharacter player, int popupID) { + + ErrorPopupMsg errorPopup; + Dispatch errorDispatch; + + if (player == null) + return; + + errorPopup = new ErrorPopupMsg(popupID); + + errorDispatch = Dispatch.borrow(player, errorPopup); + DispatchMessage.dispatchMsgDispatch(errorDispatch, Enum.DispatchChannel.SECONDARY); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.message = reader.getInt(); + reader.getInt(); + reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.message); + writer.putString(this.custom); + writer.putInt(0); + } +} diff --git a/src/engine/net/client/msg/FriendRequestMsg.java b/src/engine/net/client/msg/FriendRequestMsg.java new file mode 100644 index 00000000..ad735157 --- /dev/null +++ b/src/engine/net/client/msg/FriendRequestMsg.java @@ -0,0 +1,64 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class FriendRequestMsg extends ClientNetMsg { + + public String sourceName; + public String friendName; + + /** + * This is the general purpose constructor. + */ + public FriendRequestMsg(PlayerCharacter pc) { + super(Protocol.PLAYERFRIENDS); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public FriendRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.PLAYERFRIENDS, origin, reader); + } + + /** + * Copy constructor + */ + public FriendRequestMsg(FriendRequestMsg msg) { + super(Protocol.PLAYERFRIENDS); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //Do we even want to try this? + this.sourceName = reader.getString(); //This is source name.. this friends list must never been updated since launch, not using IDS. + this.friendName = reader.getString(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.sourceName); + writer.putString(this.friendName); + } +} diff --git a/src/engine/net/client/msg/FurnitureMsg.java b/src/engine/net/client/msg/FurnitureMsg.java new file mode 100644 index 00000000..b68e42b0 --- /dev/null +++ b/src/engine/net/client/msg/FurnitureMsg.java @@ -0,0 +1,289 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.math.Vector3fImmutable; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class FurnitureMsg extends ClientNetMsg { + + + private int type; + private int buildingID; + private int size; + private int pad = 0; + private int itemID; + private Vector3fImmutable furnitureLoc; + private int floor; + private float rot; + private float w; + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public FurnitureMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.FURNITURE, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + reader.getInt(); + this.buildingID = reader.getInt(); + if (this.type == 3) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.itemID = reader.getInt(); + this.furnitureLoc = reader.getVector3fImmutable(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + return; + + + } + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + + } + + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (15); + } + + + // Precache and configure this message before we serialize it + + public void configure() { + + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + if (this.type == 3 || this.type == 4) { + writer.putInt(this.type); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(GameObjectType.Item.ordinal()); + writer.putInt(62280); + writer.putVector3f(this.furnitureLoc); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + return; + } + + + writer.putInt(2); //Type + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.put((byte) 1); + writer.putInt(1); + writer.putInt(GameObjectType.Item.ordinal()); + writer.putInt(62280); + writer.putInt(0); + + writer.putInt(1); + + writer.putInt(0); + writer.putInt(362003); + writer.putInt(GameObjectType.Item.ordinal()); + writer.putInt(62280); + + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + + writer.putInt(0); + writer.putInt(0); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.put((byte) 0); + + // writer.putInt(0); + //writer.putInt(0); + +// writer.putInt(1); +// writer.putInt(GameObjectType.Building.ordinal()); +// writer.putInt(62281); +// writer.putInt(0); +// writer.putInt(362003); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.put((byte)1); +// writer.putInt(1); +// writer.putInt(GameObjectType.Building.ordinal()); +// writer.putInt(62281); +// writer.putInt(0); +// writer.putInt(362003); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.putInt(0); +// writer.put((byte)0); + + + // else{ + // writer.putInt(building.getFurnitureList().size()); + // for (int furnitureID: building.getFurnitureList()){ + // Building furniture = Building.getBuildingFromCache(furnitureID); + // writer.putInt(GameObjectType.Building.ordinal()); + // writer.putInt(furniture.getObjectUUID()); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.put((byte)0); + // } + + //} + + // else{ + // writer.putInt(building.getFurnitureList().size()); + // for (int furnitureID: building.getFurnitureList()){ + // Building furniture = Building.getBuildingFromCache(furnitureID); + // writer.putInt(GameObjectType.Building.ordinal()); + // writer.putInt(furniture.getObjectUUID()); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // writer.put((byte)0); + // } + // + // } + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public int getItemID() { + return itemID; + } + + public void setItemID(int itemID) { + this.itemID = itemID; + } + + public Vector3fImmutable getFurnitureLoc() { + return furnitureLoc; + } + + public void setFurnitureLoc(Vector3fImmutable furnitureLoc) { + this.furnitureLoc = furnitureLoc; + } + + public int getFloor() { + return floor; + } + + public void setFloor(int floor) { + this.floor = floor; + } + + public float getRot() { + return rot; + } + + public void setRot(float rot) { + this.rot = rot; + } + + public float getw() { + return w; + } + + public void setw(float w) { + this.w = w; + } + +} diff --git a/src/engine/net/client/msg/GrantExperienceMsg.java b/src/engine/net/client/msg/GrantExperienceMsg.java new file mode 100644 index 00000000..cdff6397 --- /dev/null +++ b/src/engine/net/client/msg/GrantExperienceMsg.java @@ -0,0 +1,100 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + +public class GrantExperienceMsg extends ClientNetMsg { + + private int xpGranted; + private int objectType; + private int objectID; + + /** + * This is the general purpose constructor. + */ + public GrantExperienceMsg(PlayerCharacter pc, int xpGranted) { + super(Protocol.EXPERIENCE); + this.xpGranted = xpGranted; + this.objectType = pc.getObjectType().ordinal(); + this.objectID = pc.getObjectUUID(); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GrantExperienceMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.EXPERIENCE, origin, reader); + } + + /** + * Copy constructor + */ + public GrantExperienceMsg(GrantExperienceMsg msg) { + super(Protocol.EXPERIENCE); + this.xpGranted = msg.xpGranted; + this.objectType = msg.objectType; + this.objectID = msg.objectID; + } + + /** + * Deserializes the subclass specific items from the supplied + * ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.get(); + this.xpGranted = reader.getInt(); + this.objectType = reader.getInt(); + this.objectID = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.xpGranted); + writer.putInt(this.objectType); + writer.putInt(this.objectID); + } + + public int getXPGranted() { + return this.xpGranted; + } + + public void setXPGranted(int value) { + this.xpGranted = value; + } + + public int getObjectType() { + return this.objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getObjectID() { + return this.objectID; + } + + public void setObjectID(int value) { + this.objectID = value; + } +} diff --git a/src/engine/net/client/msg/GuildTreeStatusMsg.java b/src/engine/net/client/msg/GuildTreeStatusMsg.java new file mode 100644 index 00000000..f9c662d7 --- /dev/null +++ b/src/engine/net/client/msg/GuildTreeStatusMsg.java @@ -0,0 +1,193 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; + +import java.time.LocalDateTime; + +public class GuildTreeStatusMsg extends ClientNetMsg { + + // 2 = manage this asset. 20 = manage entire city + private int targetType; + private int targetID; + private String CityName; + private String OwnerName; + private String GuildName; + + private String motto; //motto Length 60 max? + private Building treeOfLife; + private PlayerCharacter player; + private City city; + private Zone cityZone; + private GuildTag cityGuildTag; + private GuildTag cityNationTag; + private java.time.LocalDateTime cityDate; + private boolean canAccess = false; + private byte canBind = 0; + private int accessType = 0; + + /** + * This is the general purpose constructor + */ + public GuildTreeStatusMsg() { + super(Protocol.GUILDTREESTATUS); + + this.targetType = 0; + this.targetID = 0; + this.OwnerName = ""; + this.CityName = ""; + this.GuildName = ""; + this.cityGuildTag = null; + this.cityNationTag = null; + } + + public GuildTreeStatusMsg(Building treeOfLife, PlayerCharacter sourcePlayer) { + super(Protocol.GUILDTREESTATUS); + this.treeOfLife = treeOfLife; + this.player = sourcePlayer; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildTreeStatusMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GUILDTREESTATUS, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + targetType = reader.getInt(); + targetID = reader.getInt(); + + for (int i = 0; i < 3; i++) { + reader.monitorInt(0, "GuildTreeStatusMSG"); + } + } + + public void configure() { + + this.targetType = treeOfLife.getObjectType().ordinal(); + this.targetID = treeOfLife.getObjectUUID(); + this.OwnerName = treeOfLife.getOwner() != null ? treeOfLife.getOwnerName() : "Abandoned"; + this.CityName = treeOfLife.getCityName(); + this.GuildName = treeOfLife.getGuildName(); + + this.cityGuildTag = treeOfLife.getGuild().getGuildTag(); + this.cityNationTag = this.treeOfLife.getGuild().getNation().getGuildTag(); + + canAccess = this.canModify(); + canBind = 0; + + if (player.getGuild() != null && this.treeOfLife.getGuild() != null && !this.treeOfLife.getGuild().isEmptyGuild() + && player.getGuild().getNation() == this.treeOfLife.getGuild().getNation()) + canBind = 1; + + + if (this.treeOfLife.getGuild() != null && this.treeOfLife.getGuild().getOwnedCity() == null) + accessType = 9; + + //accessType not 9 not null city + if (accessType != 9) + if (this.treeOfLife.getGuild().getOwnedCity().isForceRename() && canAccess) + accessType = 10; + else + accessType = 8; + + cityZone = this.treeOfLife.getParentZone(); + city = null; + + if (cityZone != null) + if (cityZone.isPlayerCity()) + city = City.GetCityFromCache(cityZone.getPlayerCityUUID()); + else if (this.treeOfLife != null && this.treeOfLife.getGuild() != null) + city = this.treeOfLife.getGuild().getOwnedCity(); + + + if (city == null) + CityName = "None"; + else + CityName = city.getCityName(); + + if (city == null) + cityDate = LocalDateTime.now(); + else + cityDate = city.established; + + } + + private boolean canModify() { + + return this.player.getGuild() == this.treeOfLife.getGuild() && GuildStatusController.isInnerCouncil(player.getGuildStatus()); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + writer.putInt(targetType); + writer.putInt(targetID); + + if (canAccess) + writer.putInt(accessType); + + else + writer.putInt(9); + + GuildTag._serializeForDisplay(cityGuildTag, writer); + GuildTag._serializeForDisplay(cityNationTag, writer); + + writer.putString(CityName); + writer.putString(GuildName); + writer.putString(OwnerName); + + writer.putLocalDateTime(cityDate); + writer.putInt(0); + writer.putInt(0); + if (city == null) + writer.putInt(0); + else + writer.putInt(city.isOpen() ? 1 : 0); //check mark for open city + writer.putInt(0); + writer.putInt(0); + writer.put(canBind); + writer.put((byte) 0); + + if (canAccess) + writer.put((byte) 1); + else + writer.put((byte) 0); + + writer.put((byte) 0); + + writer.putInt(1); + writer.putString(city != null ? city.getDescription() : "None"); + writer.putInt(0); + } + + public int getTargetID() { + return targetID; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/msg/HirelingServiceMsg.java b/src/engine/net/client/msg/HirelingServiceMsg.java new file mode 100644 index 00000000..38202702 --- /dev/null +++ b/src/engine/net/client/msg/HirelingServiceMsg.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class HirelingServiceMsg extends ClientNetMsg { + + + public static final int SETREPAIRCOST = 2; + public int npcID; + public int buildingID; + public int messageType; + public int repairCost; + + /** + * This is the general purpose constructor. + * + * @param channel + */ + public HirelingServiceMsg(int channel) { + super(Protocol.HIRELINGSERVICE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public HirelingServiceMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.HIRELINGSERVICE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.messageType); + switch (this.messageType) { + case SETREPAIRCOST: + this.writeSetRepairCost(writer); + break; + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.messageType = reader.getInt(); + + switch (this.messageType) { + case SETREPAIRCOST: + this.readSetRepairCost(reader); + break; + + } + } + + private void readSetRepairCost(ByteBufferReader reader) { + reader.getInt(); //building type; + this.buildingID = reader.getInt(); + reader.getInt(); + this.npcID = reader.getInt(); + reader.getInt(); + reader.getInt(); //3 + this.repairCost = reader.getInt(); + reader.getInt(); + reader.getInt(); + + } + + private void writeSetRepairCost(ByteBufferWriter writer) { + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.putInt(GameObjectType.NPC.ordinal()); + writer.putInt(this.npcID); + writer.putInt(0); + writer.putInt(3); + writer.putInt(this.repairCost); + writer.putInt(0); + writer.putInt(0); + } + + private void writeTest(ByteBufferWriter writer) { + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.putInt(GameObjectType.NPC.ordinal()); + writer.putInt(this.npcID); + writer.putInt(3); + writer.putInt(1); + writer.putInt(1); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.putInt(GameObjectType.NPC.ordinal()); + writer.putInt(this.npcID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(3); + writer.putInt(1); + + writer.putInt(0); + + } +} diff --git a/src/engine/net/client/msg/HotzoneChangeMsg.java b/src/engine/net/client/msg/HotzoneChangeMsg.java new file mode 100644 index 00000000..b779e3ac --- /dev/null +++ b/src/engine/net/client/msg/HotzoneChangeMsg.java @@ -0,0 +1,91 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.gameManager.ConfigManager; +import engine.gameManager.ZoneManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + + +public class HotzoneChangeMsg extends ClientNetMsg { + + private int zoneType; + private int zoneID; + private int secondsRemaining; + + /** + * This is the general purpose constructor. + */ + public HotzoneChangeMsg(int zoneType, int zoneID) { + super(Protocol.ARCHOTZONECHANGE); + this.zoneType = zoneType; + this.zoneID = zoneID; + + int hotZoneDuration = Integer.parseInt(ConfigManager.MB_HOTZONE_DURATION.getValue()); + Instant currentInstant = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant(); + secondsRemaining = (int) Duration.between(currentInstant, ZoneManager.hotZoneLastUpdate.plusSeconds(hotZoneDuration * 3600)).getSeconds(); + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public HotzoneChangeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCHOTZONECHANGE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.zoneType); + writer.putInt(this.zoneID); + writer.putInt(secondsRemaining); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.zoneType = reader.getInt(); + this.zoneID = reader.getInt(); + reader.getInt(); + } + + public int getZoneType() { + return this.zoneType; + } + + public void setZoneType(int value) { + this.zoneType = value; + } + + public int getZoneID() { + return this.zoneID; + } + + public void setZoneID(int value) { + this.zoneID = value; + } +} diff --git a/src/engine/net/client/msg/IgnoreListMsg.java b/src/engine/net/client/msg/IgnoreListMsg.java new file mode 100644 index 00000000..cbfb0991 --- /dev/null +++ b/src/engine/net/client/msg/IgnoreListMsg.java @@ -0,0 +1,110 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +import java.util.ArrayList; + +public class IgnoreListMsg extends ClientNetMsg { + + private final ArrayList names = new ArrayList<>(); + private int playerType; + private int playerID; + private String playerName; + + /** + * This is the general purpose constructor. + */ + public IgnoreListMsg() { + super(Protocol.ARCIGNORELISTUPDATE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public IgnoreListMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCIGNORELISTUPDATE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.names.size()); + for (String name : this.names) { + writer.putString(name); + } + writer.putInt(this.playerType); + writer.putInt(this.playerID); + writer.putString(this.playerName); + writer.put((byte) 0); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + int size = reader.getInt(); + for (int i = 0; i < size; i++) { + this.names.add(reader.getString()); + } + this.playerType = reader.getInt(); + this.playerID = reader.getInt(); + this.playerName = reader.getString(); + reader.get(); + } + + public ArrayList getNames() { + return this.names; + } + + public void addName(String value) { + this.names.add(value); + } + + public void removeName(String value) { + this.names.remove(value); + } + + public int getPlayerType() { + return this.playerType; + } + + public void setPlayerType(int value) { + this.playerType = value; + } + + public int getPlayerID() { + return this.playerID; + } + + public void setPlayerID(int value) { + this.playerID = value; + } + + public String getName() { + return this.playerName; + } + + public void setName(String value) { + this.playerName = value; + } + +} diff --git a/src/engine/net/client/msg/IgnoreMsg.java b/src/engine/net/client/msg/IgnoreMsg.java new file mode 100644 index 00000000..644f8b45 --- /dev/null +++ b/src/engine/net/client/msg/IgnoreMsg.java @@ -0,0 +1,181 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.Account; +import engine.objects.PlayerCharacter; + + +public class IgnoreMsg extends ClientNetMsg { + + private int unknown1; + private int unknown2; + private String nameToIgnore; + + /** + * This is the general purpose constructor. + */ + public IgnoreMsg() { + super(Protocol.IGNORE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public IgnoreMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.IGNORE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(unknown1); + writer.putInt(unknown2); + writer.putUnicodeString(nameToIgnore); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown1 = reader.getInt(); + unknown2 = reader.getInt(); + nameToIgnore = reader.getUnicodeString(); + } + + public void handleRequest(ClientConnection origin) { + + PlayerCharacter pcSource = SessionManager.getPlayerCharacter(origin); + + if (nameToIgnore.isEmpty()) { // list ignored players + String[] ignoredPlayers = pcSource.getIgnoredPlayerNames(); + String crlf = "\r\n"; + String out = "Ignored players (" + ignoredPlayers.length + "):"; + for (String name : ignoredPlayers) { + out += crlf + name; + } + ChatManager.chatSystemInfo(pcSource, out); + return; + } + + //FIX THIS, USE OUR CACHE! + PlayerCharacter pcToIgnore = PlayerCharacter.getByFirstName(nameToIgnore); + + if (pcSource == null) { + return; + } + if (pcToIgnore == null || pcToIgnore.getAccount() == null) { + ChatManager.chatSystemError(pcSource, "Character name " + nameToIgnore + " does not exist and cannot be ignored."); + return; + } + if (pcToIgnore.getObjectUUID() == pcSource.getObjectUUID()) { + ChatManager.chatSystemError(pcSource, "Try as you might, you are unable to ignore yourself!"); + return; + } + String fn = pcToIgnore.getFirstName(); + + Account ac = pcSource.getAccount(); + + if (ac == null) + return; + + if (pcSource.isIgnoringPlayer(pcToIgnore)) { + + if (ac != null) { + if (!DbManager.PlayerCharacterQueries.SET_IGNORE_LIST(ac.getObjectUUID(), pcToIgnore.getObjectUUID(), false, pcToIgnore.getFirstName())) { + ChatManager.chatSystemError(pcSource, "Unable to update database ignore list."); + } + } else { + ChatManager.chatSystemError(pcSource, "Unable to update database ignore list."); + } + + pcSource.removeIgnoredPlayer(pcToIgnore.getAccount()); + ChatManager.chatSystemInfo(pcSource, "Character " + fn + " is no longer ignored."); + } else { + if (!PlayerCharacter.isIgnorable()) { + ChatManager.chatSystemError(pcSource, "This character cannot be ignored."); + return; + } + if (PlayerCharacter.isIgnoreListFull()) { + ChatManager.chatSystemError(pcSource, "Your ignore list is already full."); + return; + } + + if (ac != null) { + if (!DbManager.PlayerCharacterQueries.SET_IGNORE_LIST(ac.getObjectUUID(), pcToIgnore.getObjectUUID(), true, pcToIgnore.getFirstName())) { + ChatManager.chatSystemError(pcSource, "Unable to update database ignore list. This ignore will not persist past server down."); + } + } else { + ChatManager.chatSystemError(pcSource, "Unable to update database ignore list."); + } + + pcSource.addIgnoredPlayer(pcToIgnore.getAccount(), pcToIgnore.getFirstName()); + ChatManager.chatSystemInfo(pcSource, "Character " + fn + " is now being ignored."); + } + } + + /** + * @return the unknown1 + */ + public int getUnknown1() { + return unknown1; + } + + /** + * @param unknown1 the unknown1 to set + */ + public void setUnknown1(int unknown1) { + this.unknown1 = unknown1; + } + + /** + * @return the unknown2 + */ + public int getUnknown2() { + return unknown2; + } + + /** + * @param unknown2 the unknown2 to set + */ + public void setUnknown2(int unknown2) { + this.unknown2 = unknown2; + } + + /** + * @return the nameToIgnore + */ + public String getNameToIgnore() { + return nameToIgnore; + } + + /** + * @param nameToIgnore the nameToIgnore to set + */ + public void setNameToIgnore(String nameToIgnore) { + this.nameToIgnore = nameToIgnore; + } + +} diff --git a/src/engine/net/client/msg/InvalidTradeRequestMsg.java b/src/engine/net/client/msg/InvalidTradeRequestMsg.java new file mode 100644 index 00000000..4e97395b --- /dev/null +++ b/src/engine/net/client/msg/InvalidTradeRequestMsg.java @@ -0,0 +1,96 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Invalid trade request. + * Attempt to trade with player who is already trading. + */ + +public class InvalidTradeRequestMsg extends ClientNetMsg { + + private int unknown01; + private int busyType; + private int busyID; + private int requesterType; + private int requesterID; + + /** + * This is the general purpose constructor + */ + public InvalidTradeRequestMsg(int unknown01, AbstractGameObject busy, AbstractGameObject requester) { + super(Protocol.ARCREQUESTTRADEBUSY); + this.busyType = busy.getObjectType().ordinal(); + this.busyID = busy.getObjectUUID(); + this.requesterType = requester.getObjectType().ordinal(); + this.requesterID = requester.getObjectUUID(); + this.unknown01 = unknown01; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public InvalidTradeRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCREQUESTTRADEBUSY, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + busyType = reader.getInt(); + busyID = reader.getInt(); + requesterType = reader.getInt(); + requesterID = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putInt(busyType); + writer.putInt(busyID); + writer.putInt(requesterType); + writer.putInt(requesterID); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + public int getRequesterID() { + return requesterID; + } + +} diff --git a/src/engine/net/client/msg/ItemEffectMsg.java b/src/engine/net/client/msg/ItemEffectMsg.java new file mode 100644 index 00000000..5408118c --- /dev/null +++ b/src/engine/net/client/msg/ItemEffectMsg.java @@ -0,0 +1,266 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ItemEffectMsg extends ClientNetMsg { + + protected int numTrains; + protected int effectID; + protected int sourceType; + protected int sourceID; + protected int targetType; + protected int targetID; + + protected int unknown02; + protected int unknown03; + protected int duration; + protected int unknown05; + protected byte unknown06; + protected int ItemType; + protected int powerTypeID; + protected String powerUsedName; + protected int itemID; + + /** + * This is the general purpose constructor. + */ + public ItemEffectMsg() { + super(Protocol.POWERACTION); + this.numTrains = 0; + this.effectID = 0; + this.sourceType = 0; + this.sourceID = 0; + this.targetType = 0; + this.targetID = 0; + + this.unknown02 = 0; + this.unknown03 = 0; + this.duration = 0; + this.unknown05 = 0; + this.unknown06 = (byte) 0; + + this.ItemType = 0; + this.itemID = 0; + this.powerUsedName = ""; + } + + /** + * This is the general purpose constructor. + */ + public ItemEffectMsg(AbstractWorldObject source, AbstractWorldObject target, int numTrains, int effectID, int duration, + int powerUsedID, String powerUsedName) { + super(Protocol.POWERACTION); + this.numTrains = numTrains; + this.effectID = effectID; + + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + } else { + this.sourceType = 0; + this.sourceID = 0; + } + + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + } else { + this.targetType = 0; + this.targetID = 0; + } + this.unknown02 = 0; //0 = apply, 2= remove + this.unknown03 = 0; + this.duration = duration; + this.unknown05 = 0; //1 = remove item effect + this.unknown06 = (byte) 0; //1 = remove item effect + this.ItemType = powerUsedID; + this.itemID = 0; + this.powerUsedName = powerUsedName; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ItemEffectMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.POWERACTION, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.numTrains); + writer.putInt(this.effectID); + + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.duration); + writer.putInt(this.unknown05); + writer.put(this.unknown06); + + writer.putInt(this.ItemType); + writer.putInt(this.itemID); + + writer.putString(this.powerUsedName); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.numTrains = reader.getInt(); + this.effectID = reader.getInt(); + + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.duration = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.get(); + + this.ItemType = reader.getInt(); + this.itemID = reader.getInt(); + + this.powerUsedName = reader.getString(); + } + + public int getNumTrains() { + return this.numTrains; + } + + public void setNumTrains(int value) { + this.numTrains = value; + } + + public int getEffectID() { + return this.effectID; + } + + public void setEffectID(int value) { + this.effectID = value; + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public int getTargetType() { + return this.targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return this.targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public int getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getUnknown03() { + return this.unknown03; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public int getDuration() { + return this.duration; + } + + public void setDuration(int value) { + this.duration = value; + } + + public int getUnknown05() { + return this.unknown05; + } + + public void setUnknown05(int value) { + this.unknown05 = value; + } + + public byte getUnknown06() { + return this.unknown06; + } + + public void setUnknown06(byte value) { + this.unknown06 = value; + } + + public int getItemType() { + return this.ItemType; + } + + public void setItemType(int value) { + this.ItemType = value; + } + + public int getItemID() { + return this.itemID; + } + + public void setItemID(int value) { + this.itemID = value; + } + + public String getPowerUsedName() { + return this.powerUsedName; + } + + public void setPowerUsedName(String value) { + this.powerUsedName = value; + } +} diff --git a/src/engine/net/client/msg/ItemHealthUpdateMsg.java b/src/engine/net/client/msg/ItemHealthUpdateMsg.java new file mode 100644 index 00000000..ba818e3d --- /dev/null +++ b/src/engine/net/client/msg/ItemHealthUpdateMsg.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ItemHealthUpdateMsg extends ClientNetMsg { + + private int slot; + private float newDurability; + + /** + * This is the general purpose constructor. + */ + public ItemHealthUpdateMsg(int slot, float newDurability) { + super(Protocol.ITEMHEALTHUPDATE); + this.slot = slot; + this.newDurability = newDurability; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ItemHealthUpdateMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ITEMHEALTHUPDATE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.slot); + writer.putFloat(this.newDurability); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.slot = reader.getInt(); + this.newDurability = reader.getFloat(); + } + + public int getSlot() { + return this.slot; + } + + public void setSlot(int value) { + this.slot = value; + } + + public float getNewDurability() { + return this.newDurability; + } + + public void setNewDurability(float value) { + this.newDurability = value; + } +} diff --git a/src/engine/net/client/msg/ItemProductionMsg.java b/src/engine/net/client/msg/ItemProductionMsg.java new file mode 100644 index 00000000..dc7ed812 --- /dev/null +++ b/src/engine/net/client/msg/ItemProductionMsg.java @@ -0,0 +1,568 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.gameManager.BuildingManager; +import engine.gameManager.PowersManager; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; +import engine.objects.Item; +import engine.objects.MobLoot; +import engine.objects.NPC; +import engine.powers.EffectsBase; + +import java.util.ArrayList; +import java.util.HashMap; + +public class ItemProductionMsg extends ClientNetMsg { + + private static final int ACTION_PRODUCE = 1; + private static final int ACTION_JUNK = 2; + private static final int ACTION_RECYCLE = 3; + private static final int ACTION_COMPLETE = 4; + private static final int ACTION_DEPOSIT = 6; + private static final int ACTION_SETPRICE = 5; + private static final int ACTION_TAKE = 7; + private static final int ACTION_CONFIRM_SETPRICE = 9; + private static final int ACTION_CONFIRM_DEPOSIT = 10; + private static final int ACTION_CONFIRM_TAKE = 11; // Unsure. Sent by client + private static final int ACTION_CONFIRM_PRODUCE = 8; + + private ArrayList ItemList; + private int size; + private int buildingUUID; + private int unknown01; + private int itemUUID; + private int itemType; + private int totalProduction; + private int unknown03; + private int pToken; + private int sToken; + private String name; + private int actionType; + private int npcUUID; + private boolean add; + private int itemPrice; + private boolean isMultiple; + private HashMap itemIDtoTypeMap; + + /** + * This is the general purpose constructor. + */ + + public ItemProductionMsg() { + super(Protocol.ITEMPRODUCTION); + this.actionType = 0; + this.size = 0; + this.buildingUUID = 0; + this.unknown01 = 0; + this.itemUUID = 0; + this.totalProduction = 0; + this.unknown03 = 0; + this.pToken = 0; + this.sToken = 0; + this.name = ""; + this.itemPrice = 0; + this.itemType = 0; + + } + + ; + + public ItemProductionMsg(Building building, NPC vendor, Item item, int actionType, boolean add) { + super(Protocol.ITEMPRODUCTION); + this.actionType = actionType; + this.size = 0; + this.buildingUUID = building.getObjectUUID(); + this.npcUUID = vendor.getObjectUUID(); + this.itemType = item.getObjectType().ordinal(); + this.itemUUID = item.getObjectUUID(); + this.unknown01 = 0; + this.totalProduction = 0; + this.unknown03 = 0; + this.pToken = 0; + this.sToken = 0; + this.name = ""; + this.add = add; + this.itemPrice = item.getValue(); + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ItemProductionMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ITEMPRODUCTION, origin, reader); + + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + //Larger size for historically larger opcodes + return (16); // 2^16 == 64k + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + Building building = BuildingManager.getBuildingFromCache(this.buildingUUID); + if (building == null) + return; + // Common Header + + writer.putInt(this.actionType); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingUUID); + writer.putInt(GameObjectType.NPC.ordinal()); + writer.putInt(this.npcUUID); + + switch (this.actionType) { + + case ACTION_CONFIRM_DEPOSIT: + writer.putInt(0); // Not item ordinal? + writer.putInt(0); // Not item uuid? + writer.putInt(1); + writer.putInt(0); + + if (!add) { + writer.put((byte) 1); + Item item = Item.getFromCache(this.itemUUID); + if (item != null) + Item.serializeForClientMsgWithoutSlot(item, writer); + writer.putInt(building.getStrongboxValue()); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + break; + } + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); + Item item; + if (this.itemType == GameObjectType.Item.ordinal()) + item = Item.getFromCache(this.itemUUID); + else + item = MobLoot.getFromCache(this.itemUUID); + if (item != null) + Item.serializeForClientMsgWithoutSlot(item, writer); + writer.putInt(building.getStrongboxValue()); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + break; + case ACTION_CONFIRM_TAKE: + writer.putInt(this.itemType); + writer.putInt(this.itemUUID); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + break; + case ACTION_SETPRICE: + writer.putInt(this.itemType); + writer.putInt(this.itemUUID); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(this.itemPrice); // new price + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + break; + case ACTION_CONFIRM_SETPRICE: + writer.putInt(this.itemType); + writer.putInt(this.itemUUID); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); + writer.putInt(building.getStrongboxValue()); // new price + writer.putInt(0); + writer.putInt(0); + //writer.put((byte) 0); + break; + case ACTION_DEPOSIT: + writer.putInt(this.itemType); + writer.putInt(this.itemUUID); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + break; + case ACTION_TAKE: + case ACTION_RECYCLE: + + writer.putInt(0); + writer.putInt(0); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); + writer.putInt(building.getStrongboxValue()); + + if (this.itemIDtoTypeMap != null) { + writer.putInt(this.itemIDtoTypeMap.size()); + + for (int itemID : this.itemIDtoTypeMap.keySet()) { + writer.putInt(this.itemIDtoTypeMap.get(itemID)); + writer.putInt(itemID); + } + + } else + writer.putInt(0); + + writer.putInt(0); + + break; + case ACTION_CONFIRM_PRODUCE: + writer.putInt(0); + writer.putInt(0); + writer.putInt(1); + MobLoot toRoll = MobLoot.getFromCache(this.itemUUID); + writer.putInt(-1497023830); + if (toRoll != null && toRoll.getPrefix() != null && !toRoll.getPrefix().isEmpty()) { + EffectsBase eb = PowersManager.getEffectByIDString(toRoll.getPrefix()); + if (eb == null) + this.pToken = 0; + else + this.pToken = eb.getToken(); + } + + if (toRoll != null && toRoll.getSuffix() != null && !toRoll.getSuffix().isEmpty()) { + EffectsBase eb = PowersManager.getEffectByIDString(toRoll.getSuffix()); + if (eb == null) + this.sToken = 0; + else + this.sToken = eb.getToken(); + } + if (toRoll.isRandom() == false || (toRoll != null && toRoll.isComplete())) { + writer.putInt(this.pToken); + writer.putInt(this.sToken); + } else { + writer.putInt(0); + writer.putInt(0); + } + + writer.putString(toRoll.getCustomName()); + ; + writer.putInt(GameObjectType.MobLoot.ordinal()); + writer.putInt(this.itemUUID); + writer.putInt(0); //items left to produce? + if (toRoll != null) { + writer.putInt(toRoll.getItemBaseID()); + writer.putInt(toRoll.getValue()); + } else { + writer.putInt(0); + writer.putInt(0); + } + + NPC vendor = NPC.getFromCache(this.npcUUID); + if (vendor != null) { + if (toRoll.isComplete()) { + writer.putInt(0); + writer.putInt(0); + } else { + long timeLeft = toRoll.getDateToUpgrade() - System.currentTimeMillis(); + + timeLeft /= 1000; + writer.putInt((int) timeLeft); + writer.putInt(vendor.getRollingTimeInSeconds(toRoll.getItemBaseID())); + } + + } else { + writer.putInt(0); + writer.putInt(0); + } + if (toRoll.isComplete()) + writer.putInt(0); + else + writer.putInt(1); + writer.put((byte) 0); + + if (toRoll != null && toRoll.isComplete()) + writer.put((byte) 1); + else + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 1); + writer.putInt(vendor.getBuilding().getStrongboxValue()); + + writer.putInt(0); + writer.putInt(0); + //writer.putInt(0); //error popup + + break; + case ACTION_COMPLETE: + writer.putInt(this.itemType); + writer.putInt(this.itemUUID); + writer.putInt(this.totalProduction); + writer.putInt(this.unknown03); + writer.putInt(this.pToken); + writer.putInt(this.sToken); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + break; + case ACTION_JUNK: + writer.putInt(this.itemType); + writer.putInt(this.itemUUID); + writer.putInt(this.totalProduction); + writer.putInt(this.unknown03); + writer.putInt(this.pToken); + writer.putInt(this.sToken); + writer.putString(this.name); + writer.putInt(0); + writer.putInt(0); + writer.putShort((short) 0); + writer.put((byte) 0); + break; + default: + writer.putInt(0); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + break; + } + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + // Common header + + this.actionType = reader.getInt(); + reader.getInt(); // Building type padding + this.buildingUUID = reader.getInt(); + reader.getInt(); // NPC type padding + this.npcUUID = reader.getInt(); + + switch (this.actionType) { + case ACTION_SETPRICE: + this.itemType = reader.getInt(); + this.itemUUID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.itemPrice = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + break; + case ACTION_RECYCLE: + case ACTION_TAKE: + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + this.size = reader.getInt(); + HashMap tempIDs = new HashMap<>(); + for (int i = 0; i < this.size; i++) { + int type = reader.getInt(); // Item type padding + this.itemUUID = reader.getInt(); + tempIDs.put(this.itemUUID, type); + } + reader.getInt(); + this.itemIDtoTypeMap = tempIDs; + break; + case ACTION_DEPOSIT: + this.itemType = reader.getInt(); + this.itemUUID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + break; + case ACTION_JUNK: + this.itemType = reader.getInt(); + this.itemUUID = reader.getInt(); + this.totalProduction = reader.getInt(); + this.unknown03 = reader.getInt(); + this.pToken = reader.getInt(); + this.sToken = reader.getInt(); + this.name = reader.getString(); + reader.getInt(); + reader.getInt(); + reader.get(); + break; + default: + this.itemType = reader.getInt(); + this.itemUUID = reader.getInt(); + this.totalProduction = reader.getInt(); + this.unknown03 = reader.getInt(); + this.pToken = reader.getInt(); + this.sToken = reader.getInt(); + this.name = reader.getString(); + this.isMultiple = reader.getInt() != 0 ? true : false; + reader.getInt(); + + if (this.actionType == ACTION_COMPLETE || this.actionType == ACTION_JUNK) + reader.get(); + else + reader.getShort(); + break; + + } + } + + public int getItemType() { + return itemType; + } + + // TODO fix ArrayList Accessability. + public ArrayList getItemList() { + return this.ItemList; + } + + public void setItemList(ArrayList value) { + this.ItemList = value; + } + + public void addItem(Long item) { + this.ItemList.add(item); + } + + public int getUnknown01() { + return unknown01; + } + + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + public int getTotalProduction() { + return totalProduction; + } + + public void setTotalProduction(int unknown02) { + this.totalProduction = unknown02; + } + + public int getUnknown03() { + return unknown03; + } + + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + public int getItemUUID() { + return itemUUID; + } + + public void setItemUUID(int itemUUID) { + this.itemUUID = itemUUID; + } + + public int getpToken() { + return pToken; + } + + public void setpToken(int pToken) { + this.pToken = pToken; + } + + public int getsToken() { + return sToken; + } + + public void setsToken(int sToken) { + this.sToken = sToken; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public int getActionType() { + return actionType; + } + + public final void setActionType(int actionType) { + this.actionType = actionType; + } + + public int getNpcUUID() { + return npcUUID; + } + + public HashMap getItemIDtoTypeMap() { + return itemIDtoTypeMap; + } + + /** + * @return the itemPrice + */ + public int getItemPrice() { + return itemPrice; + } + + public boolean isMultiple() { + return isMultiple; + } + + public void setMultiple(boolean isMultiple) { + this.isMultiple = isMultiple; + } +} diff --git a/src/engine/net/client/msg/KeepAliveServerClientMsg.java b/src/engine/net/client/msg/KeepAliveServerClientMsg.java new file mode 100644 index 00000000..e674d1b5 --- /dev/null +++ b/src/engine/net/client/msg/KeepAliveServerClientMsg.java @@ -0,0 +1,86 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class KeepAliveServerClientMsg extends ClientNetMsg { + + private int firstData; // First 4 bytes + private int secondData; // Second 4 bytes + + private double timeLoggedIn; + private byte endData; // Last byte + + /** + * This is the general purpose constructor. + */ + public KeepAliveServerClientMsg(int firstData, int secondData, byte endData) { + super(Protocol.KEEPALIVESERVERCLIENT); + this.firstData = firstData; + this.secondData = secondData; + this.endData = endData; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public KeepAliveServerClientMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.KEEPALIVESERVERCLIENT, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putDouble(this.timeLoggedIn); + writer.put((byte) 0); + //writer.putDouble(1000); + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.timeLoggedIn = reader.getDouble(); + this.endData = reader.get(); + } + + public int getFirstData() { + return firstData; + } + + public int getSecondData() { + return secondData; + } + + public byte getEndData() { + return endData; + } + + public double getTimeLoggedIn() { + return timeLoggedIn; + } + + public void setTimeLoggedIn(double timeLoggedIn) { + this.timeLoggedIn = timeLoggedIn; + } +} diff --git a/src/engine/net/client/msg/LeaderboardMessage.java b/src/engine/net/client/msg/LeaderboardMessage.java new file mode 100644 index 00000000..3e2bc9ce --- /dev/null +++ b/src/engine/net/client/msg/LeaderboardMessage.java @@ -0,0 +1,104 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.ShrineType; +import engine.exception.SerializationException; +import engine.gameManager.BuildingManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; +import engine.objects.Guild; +import engine.objects.GuildTag; +import engine.objects.Shrine; + +public class LeaderboardMessage extends ClientNetMsg { + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LeaderboardMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LEADERBOARD, origin, reader); + } + + public LeaderboardMessage() { + super(Protocol.LEADERBOARD); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putInt(ShrineType.values().length);//?? + + for (ShrineType shrineType : ShrineType.values()) { + writer.putInt(shrineType.ordinal()); + writer.putInt(shrineType.getShrinesCopy().size()); + int i = 0; + for (Shrine shrine : shrineType.getShrinesCopy()) { + i++; + writer.putInt(shrine.getFavors()); + Building shrineBuilding = BuildingManager.getBuilding(shrine.getBuildingID()); + if (shrineBuilding != null) { + Guild shrineGuild = shrineBuilding.getGuild(); + if (shrineGuild != null) { + writer.putInt(shrineGuild.getObjectType().ordinal()); + writer.putInt(shrineGuild.getObjectUUID()); + + GuildTag._serializeForDisplay(shrineGuild.getGuildTag(), writer); + writer.putString(shrineGuild.getName()); + } else { + writer.putLong(0); + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + writer.putString(""); + } + } else { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + } + } + writer.putString(shrineType.name()); + } + + } + + @Override + protected int getPowerOfTwoBufferSize() { + return (14); // 2^14 == 16384 + } + +} diff --git a/src/engine/net/client/msg/LeaveWorldMsg.java b/src/engine/net/client/msg/LeaveWorldMsg.java new file mode 100644 index 00000000..0711c881 --- /dev/null +++ b/src/engine/net/client/msg/LeaveWorldMsg.java @@ -0,0 +1,52 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class LeaveWorldMsg extends ClientNetMsg { + + /** + * This is the general purpose constructor. + */ + public LeaveWorldMsg() { + super(Protocol.LEAVEREQUEST); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LeaveWorldMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LEAVEREQUEST, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + +} diff --git a/src/engine/net/client/msg/LoadCharacterMsg.java b/src/engine/net/client/msg/LoadCharacterMsg.java new file mode 100644 index 00000000..50399a6f --- /dev/null +++ b/src/engine/net/client/msg/LoadCharacterMsg.java @@ -0,0 +1,139 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.Corpse; + +public class LoadCharacterMsg extends ClientNetMsg { + + private AbstractCharacter absChar; + private Corpse corpse; + private boolean hideNonAscii; + + /** + * This is the general purpose constructor. + */ + public LoadCharacterMsg(AbstractCharacter ch, boolean laln) { + super(Protocol.LOADCHARACTER); + this.absChar = ch; + this.corpse = null; + this.hideNonAscii = laln; + } + + /** + * This is the general purpose constructor. + */ + public LoadCharacterMsg(Corpse corpse, boolean laln) { + super(Protocol.LOADCHARACTER); + this.corpse = corpse; + this.absChar = null; + this.hideNonAscii = laln; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LoadCharacterMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LOADCHARACTER, origin, reader); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + //Larger size for historically larger opcodes + return (17); // 2^17 == 131,072 + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + if (absChar != null) { + + if (absChar.region != null) { + writer.putInt(absChar.region.getLevel()); + writer.putInt(absChar.region.getRoom()); + } else { + writer.putInt(-1); + writer.putInt(-1); + } + + } else if (corpse != null) { + writer.putInt(-1); + writer.putInt(-1); + } + + if (corpse != null) { + writer.putInt(Float.floatToIntBits(corpse.getLoc().getX())); + writer.putInt(Float.floatToIntBits(corpse.getLoc().getY())); + writer.putInt(Float.floatToIntBits(corpse.getLoc().getZ())); + writer.put((byte) 0); + } else if (absChar != null) { + + writer.putFloat(absChar.getLoc().getX()); + writer.putFloat(absChar.getLoc().getY()); + writer.putFloat(absChar.getLoc().getZ()); + + if (absChar.isMoving()) { + writer.put((byte) 1); + writer.putFloat(absChar.getEndLoc().x); + writer.putFloat(absChar.getEndLoc().y); + writer.putFloat(absChar.getEndLoc().z); + } else + writer.put((byte) 0); + } else { + writer.put((byte) 0); + } + + if (corpse != null) + Corpse._serializeForClientMsg(corpse, writer, this.hideNonAscii); + else if (absChar != null) + AbstractCharacter.serializeForClientMsgOtherPlayer(this.absChar, writer, this.hideNonAscii); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + int unknown1 = reader.getInt(); + int unknown2 = reader.getInt(); + int unknown3 = reader.getInt(); + int unknown4 = reader.getInt(); + int unknown5 = reader.getInt(); + // TODO finish deserialization impl + } + + public AbstractCharacter getChar() { + return this.absChar; + } + + public void setChar(AbstractCharacter value) { + this.absChar = value; + } + + public void setCorpse(Corpse value) { + this.corpse = value; + } +} diff --git a/src/engine/net/client/msg/LoadStructureMsg.java b/src/engine/net/client/msg/LoadStructureMsg.java new file mode 100644 index 00000000..fe8014d2 --- /dev/null +++ b/src/engine/net/client/msg/LoadStructureMsg.java @@ -0,0 +1,104 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; + +import java.util.ArrayList; + + +public class LoadStructureMsg extends ClientNetMsg { + + private ArrayList structureList; + + /** + * This is the general purpose constructor. + */ + public LoadStructureMsg() { + this(new ArrayList<>()); + } + + /** + * This is the general purpose constructor. + * + * @param structures + */ + public LoadStructureMsg(ArrayList structures) { + super(Protocol.LOADSTRUCTURE); + this.structureList = structures; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LoadStructureMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LOADSTRUCTURE, origin, reader); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + //Larger size for historically larger opcodes + return (18); // 2^16 == 64k + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + for (int i = 0; i < 4; i++) + writer.putInt(0); + + writer.putInt(this.structureList.size()); + + for (Building building : this.structureList) { + Building._serializeForClientMsg(building, writer); + } + writer.putInt(0); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + int size = reader.getInt(); + // TODO finish Deserialize impl + } + + // TODO fix ArrayList Accessability. + public ArrayList getStructureList() { + return this.structureList; + } + + public void addObject(Building obj) { + this.structureList.add(obj); + } + + public int size() { + return this.structureList.size(); + } +} diff --git a/src/engine/net/client/msg/LockUnlockDoorMsg.java b/src/engine/net/client/msg/LockUnlockDoorMsg.java new file mode 100644 index 00000000..b644be14 --- /dev/null +++ b/src/engine/net/client/msg/LockUnlockDoorMsg.java @@ -0,0 +1,111 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class LockUnlockDoorMsg extends ClientNetMsg { + + private int doorID; + private long targetID; + private int unk1; + private int unk2; + + /** + * This is the general purpose constructor. + */ + public LockUnlockDoorMsg() { + super(Protocol.LOCKUNLOCKDOOR); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LockUnlockDoorMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LOCKUNLOCKDOOR, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(doorID); + writer.putLong(targetID); + writer.putInt(unk1); + writer.putInt(unk2); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + this.doorID = reader.getInt(); + this.targetID = reader.getLong(); + this.unk1 = reader.getInt(); + this.unk2 = reader.getInt(); + } + + /** + * @return the unknown1 + */ + public int getDoorID() { + return doorID; + } + + /** + * @param unknown1 the unknown1 to set + */ + public void setDoorID(int doorID) { + this.doorID = doorID; + } + + /** + * @return the targetID + */ + public long getTargetID() { + return targetID; + } + + /** + * @param targetID the targetID to set + */ + public void setTargetID(long targetID) { + this.targetID = targetID; + } + + public int getUnk1() { + return this.unk1; + } + + public void setUnk1(int value) { + this.unk1 = value; + } + + public int getUnk2() { + return this.unk2; + } + + public void setUnk2(int value) { + this.unk2 = value; + } + +} diff --git a/src/engine/net/client/msg/LoginToGameServerMsg.java b/src/engine/net/client/msg/LoginToGameServerMsg.java new file mode 100644 index 00000000..e77639aa --- /dev/null +++ b/src/engine/net/client/msg/LoginToGameServerMsg.java @@ -0,0 +1,103 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class LoginToGameServerMsg extends ClientNetMsg { + + private String secKey; + private int Unknown01; + private int Unknown02; + + /** + * This is the general purpose constructor. + */ + public LoginToGameServerMsg() { + super(Protocol.LOGINTOGAMESERVER); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LoginToGameServerMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LOGINTOGAMESERVER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putHexString(this.secKey); + writer.putInt(this.Unknown01); + writer.putInt(this.Unknown02); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.secKey = reader.getHexString(); + + this.Unknown01 = reader.monitorInt(1065353216, "ValidateGameServerMsg 01"); + this.Unknown02 = reader.monitorInt(1065353216, "ValidateGameServerMsg 02"); + } + + /** + * @return the secKey + */ + public String getSecKey() { + return secKey; + } + + /** + * @param secKey the secKey to set + */ + public void setSecKey(String secKey) { + this.secKey = secKey; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return Unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + Unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return Unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + Unknown02 = unknown02; + } +} diff --git a/src/engine/net/client/msg/LootMsg.java b/src/engine/net/client/msg/LootMsg.java new file mode 100644 index 00000000..f2b8d355 --- /dev/null +++ b/src/engine/net/client/msg/LootMsg.java @@ -0,0 +1,267 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Item; +import engine.objects.MobEquipment; + +public class LootMsg extends ClientNetMsg { + + private Item item; + private int sourceType1; + private int sourceID1; + private int targetType; + private int targetID; + private int sourceType2; + private int sourceID2; + + private int unknown01; + private int unknown02; + private int unknown03; + private int unknown04; + private int unknown05; + private byte unknown06 = (byte) 0; + private int unknown07; + private int unknown08; + + private MobEquipment mobEquipment = null; + + /** + * This is the general purpose constructor. + */ + public LootMsg(int sourceType, int sourceID, int targetType, int targetID, Item item) { + super(Protocol.MOVEOBJECTTOCONTAINER); + this.sourceType1 = sourceType; + this.sourceID1 = sourceID; + this.targetType = targetType; + this.targetID = targetID; + this.sourceType2 = sourceType; + this.sourceID2 = sourceID; + this.item = item; + this.unknown01 = 0; + this.unknown02 = 0; + this.unknown03 = 0; + this.unknown04 = 0; + this.unknown05 = 0; + this.unknown07 = 0; + this.unknown08 = 0; + } + + //for MobEquipment + + public LootMsg(int sourceType, int sourceID, int targetType, int targetID, MobEquipment mobEquipment) { + super(Protocol.MOVEOBJECTTOCONTAINER); + this.sourceType1 = sourceType; + this.sourceID1 = sourceID; + this.targetType = targetType; + this.targetID = targetID; + this.sourceType2 = sourceType; + this.sourceID2 = sourceID; + this.item = null; + this.mobEquipment = mobEquipment; + + this.unknown01 = 0; + this.unknown02 = 0; + this.unknown03 = 0; + this.unknown04 = 0; + this.unknown05 = 0; + this.unknown07 = 0; + this.unknown08 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LootMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MOVEOBJECTTOCONTAINER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + if (this.item != null) + Item.serializeForClientMsgWithoutSlot(this.item, writer); + else if (this.mobEquipment != null) + try { + MobEquipment._serializeForClientMsg(this.mobEquipment, writer, false); + } catch (SerializationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + writer.putInt(this.sourceType1); + writer.putInt(this.sourceID1); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.sourceType2); + writer.putInt(this.sourceID2); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + writer.put(this.unknown06); + writer.putInt(this.unknown07); + writer.putInt(this.unknown08); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.item = Item.deserializeFromClientMsg(reader, false); + this.sourceType1 = reader.getInt(); + this.sourceID1 = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.sourceType2 = reader.getInt(); + this.sourceID2 = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.get(); + this.unknown07 = reader.getInt(); + this.unknown08 = reader.getInt(); + } + + public int getSourceType1() { + return this.sourceType1; + } + + public void setSourceType1(int value) { + this.sourceType1 = value; + } + + public int getSourceID1() { + return this.sourceID1; + } + + public void setSourceID1(int value) { + this.sourceID1 = value; + } + + public int getTargetType() { + return this.targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return this.targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public int getSourceType2() { + return this.sourceType2; + } + + public void setSourceType2(int value) { + this.sourceType2 = value; + } + + public int getSourceID2() { + return this.sourceID2; + } + + public void setSourceID2(int value) { + this.sourceID2 = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getUnknown03() { + return this.unknown03; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public int getUnknown04() { + return this.unknown04; + } + + public void setUnknown04(int value) { + this.unknown04 = value; + } + + public int getUnknown05() { + return this.unknown05; + } + + public void setUnknown05(int value) { + this.unknown05 = value; + } + + public byte getUnknown06() { + return this.unknown06; + } + + public void setUnknown06(byte value) { + this.unknown06 = value; + } + + public int getUnknown07() { + return this.unknown07; + } + + public void setUnknown07(int value) { + this.unknown07 = value; + } + + public int getUnknown08() { + return this.unknown08; + } + + public void setUnknown08(int value) { + this.unknown08 = value; + } + + public Item getItem() { + return this.item; + } + + public void setItem(Item value) { + this.item = value; + } +} diff --git a/src/engine/net/client/msg/LootWindowRequestMsg.java b/src/engine/net/client/msg/LootWindowRequestMsg.java new file mode 100644 index 00000000..94832bec --- /dev/null +++ b/src/engine/net/client/msg/LootWindowRequestMsg.java @@ -0,0 +1,112 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class LootWindowRequestMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private int targetType; + private int targetID; + + /** + * This is the general purpose constructor. + */ + public LootWindowRequestMsg(int sourceType, int sourceID, int targetType, int targetID) { + super(Protocol.REQUESTCONTENTS); + this.sourceType = sourceType; + this.sourceID = sourceID; + this.targetType = targetType; + this.targetID = targetID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LootWindowRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTCONTENTS, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + +} diff --git a/src/engine/net/client/msg/LootWindowResponseMsg.java b/src/engine/net/client/msg/LootWindowResponseMsg.java new file mode 100644 index 00000000..c6d42530 --- /dev/null +++ b/src/engine/net/client/msg/LootWindowResponseMsg.java @@ -0,0 +1,105 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Item; + +import java.util.ArrayList; + + +public class LootWindowResponseMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + private ArrayList inventory; + private int unknown01 = 45; + + /** + * This is the general purpose constructor. + */ + public LootWindowResponseMsg(int targetType, int targetID, ArrayList inventory) { + super(Protocol.WEIGHTINVENTORY); + this.targetType = targetType; + this.targetID = targetID; + this.inventory = inventory; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public LootWindowResponseMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.WEIGHTINVENTORY, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.put((byte) 1); + Item.putList(writer, this.inventory, false, this.targetID); + writer.putInt(this.unknown01); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public ArrayList getInventory() { + return this.inventory; + } + + public void setInventory(ArrayList value) { + this.inventory = value; + } + + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return 17; // 2^15 == 32,768 + } +} diff --git a/src/engine/net/client/msg/ManageCityAssetsMsg.java b/src/engine/net/client/msg/ManageCityAssetsMsg.java new file mode 100644 index 00000000..473082b2 --- /dev/null +++ b/src/engine/net/client/msg/ManageCityAssetsMsg.java @@ -0,0 +1,859 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.*; +import engine.gameManager.BuildingManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; +import org.joda.time.DateTime; +import org.joda.time.Period; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Open manage city asset window + */ +public class ManageCityAssetsMsg extends ClientNetMsg { + + //messageType + //C->S 2: S->C: 0, 3, 4, 6 + //C->S 15: S->C: 15 + //C->S 14: S->C: 14 + //C->S ?: S->C: 10, 11, 16 + + //C->S 2 = manage this asset + // 20 = manage entire city + + //S->C, 0 = error message + // 3 = manage asset + // 4 = no access / building info + + public int actionType; + public String assetName; + public String CityName; + public int upgradeCost; + public byte labelProtected; + public byte labelSiege; + public byte labelCeaseFire; + public byte buttonTransfer; + public byte buttonDestroy; + public byte buttonAbandon; + public byte buttonUpgrade; + private int targetID; + private int targetType; + private int targetType1; + private int targetType2; + private int targetType3; + private int targetID1; + private int targetID2; + private int targetID3; + private String AssetName1; + private int rank; + private int symbol; + private int unknown04; + private int unknown05; + private int unknown06; + private int unknown07; + private int unknown14; + private int unknown15; + private int unknown16; + private int unknown17; + private int unknown54; + private int preName01; + private byte UnkByte03; + private byte UnkByte04; + private int strongbox; + private int baneHour; + private PlayerCharacter assetManager; + private Building asset; + + /** + * This is the general purpose constructor + */ + public ManageCityAssetsMsg() { + super(Protocol.MANAGECITYASSETS); + this.actionType = 0; + this.targetType = 0; + this.targetID = 0; + this.preName01 = 0; + this.assetName = ""; + this.CityName = ""; + this.rank = 0; + this.symbol = 0; + this.unknown04 = 0; + this.unknown06 = 0; + this.unknown07 = 0; + this.unknown14 = 0; + this.unknown15 = 0; + this.unknown16 = 0; + this.unknown17 = 0; + + this.strongbox = 0; + + this.targetType1 = 0; + this.targetType2 = 0; + this.targetType3 = 0; + + this.targetID1 = 0; + this.targetID2 = 0; + this.targetID3 = 0; + this.UnkByte03 = 0; + this.UnkByte04 = 0; + this.AssetName1 = ""; + this.unknown54 = 0; + this.strongbox = 0; + this.upgradeCost = 0; + + this.labelProtected = 0; + this.labelSiege = 0; + this.labelCeaseFire = 0; + this.buttonTransfer = 0; + this.buttonDestroy = 0; + this.buttonAbandon = 0; + this.buttonUpgrade = 0; + + } + + public ManageCityAssetsMsg(PlayerCharacter pc, Building asset) { + super(Protocol.MANAGECITYASSETS); + this.assetManager = pc; + this.asset = asset; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ManageCityAssetsMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MANAGECITYASSETS, origin, reader); + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + protected int getPowerOfTwoBufferSize() { + return (20); // 2^10 == 1024 + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + public void setTargetType3(int targetType3) { + this.targetType3 = targetType3; + } + + public void setTargetID3(int targetID3) { + this.targetID3 = targetID3; + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + actionType = reader.getInt(); + targetType = reader.getInt(); + targetID = reader.getInt(); + if (this.actionType == 20) { + reader.getInt(); + this.baneHour = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + } else if (this.actionType == 5) { //rename building. + reader.getInt(); + assetName = reader.getString(); + for (int i = 0; i < 5; i++) + reader.getInt(); + } else if (this.actionType == 2) { + reader.getInt(); + this.strongbox = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + + } else { + for (int i = 0; i < 6; i++) + reader.getInt(); + } + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.actionType); + + if (this.actionType == 2) { + writer.putInt(asset.getObjectType().ordinal()); + writer.putInt(asset.getObjectUUID()); + writer.putInt(0); + writer.putInt(asset.reserve); + writer.putInt(0); + return; + } + + if (this.actionType == 13) { + writer.putInt(asset.getObjectType().ordinal()); + writer.putInt(asset.getObjectUUID()); + writer.putInt(0); + writer.putInt(0); + writer.putInt(asset.getHirelings().size()); + for (AbstractCharacter hireling : asset.getHirelings().keySet()) { + if (!hireling.getObjectType().equals(GameObjectType.NPC)) + writer.putString(hireling.getName()); + else { + NPC npc = (NPC) hireling; + if (!npc.getNameOverride().isEmpty()) { + writer.putString(npc.getNameOverride()); + } else if (npc.getContract() != null) { + if (npc.getContract().isTrainer()) { + writer.putString(npc.getName() + ", " + npc.getContract().getName()); + } else { + writer.putString(npc.getName() + " " + npc.getContract().getName()); + } + } else { + writer.putString(npc.getName()); + } + } + } + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + + //Bane window + + if (this.actionType == 11) { + writer.putInt(asset.getObjectType().ordinal()); + writer.putInt(asset.getObjectUUID()); + for (int a = 0; a < 5; a++) + writer.putInt(0); + + writer.putInt(asset.getHirelings().size()); + + for (AbstractCharacter npcHire : asset.getHirelings().keySet()) { + writer.putInt(npcHire.getObjectType().ordinal()); + writer.putInt(npcHire.getObjectUUID()); + if (npcHire.getObjectType() == GameObjectType.NPC) + writer.putString(((NPC) npcHire).getContract().getName()); + else + writer.putString("Guard Captain"); + + + writer.putString(npcHire.getName()); + writer.putInt(1); + writer.putInt(Blueprint.getNpcMaintCost(npcHire.getRank())); + if (npcHire.getObjectType() == GameObjectType.NPC) + writer.putInt(((NPC) npcHire).getContract().getIconID()); // Was 60 + else if (npcHire.getObjectType() == GameObjectType.Mob) { + writer.putInt(((Mob) npcHire).getContract().getIconID()); // Was 60 + } else + writer.putInt(5); + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 1); + writer.put((byte) 0); + writer.put((byte) 0); + } + return; + } + + if (this.actionType == 15) { + writer.putInt(1); + writer.putInt(1); + City city = null; + Zone playerZone = ZoneManager.findSmallestZone(assetManager.getLoc()); + Set buildings = ZoneManager.findSmallestZone(assetManager.getLoc()).zoneBuildingSet; + + Building tol = null; + if (playerZone.getPlayerCityUUID() != 0) + city = City.GetCityFromCache(playerZone.getPlayerCityUUID()); + + if (city != null) + tol = city.getTOL(); + + + writer.putInt(0); // 1 + String = custom message, cant control assets. + writer.putInt(0); + writer.putInt(0); //array + + writer.putInt(buildings.size()); + + int i = 0; + for (Building building : buildings) { + + i++; + writer.putString(building.getName()); //ARRAY + writer.putInt(building.getObjectType().ordinal()); //? + writer.putInt(building.getObjectUUID()); //? + + writer.putInt(4); + writer.putInt(4); + + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 1); + writer.put((byte) 1); + + //max distance to bypass clientside check. + float maxDistance = 2000; + + + writer.putFloat(maxDistance); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + if (building.getPatrolPoints() != null) { + writer.putInt(building.getPatrolPoints().size()); + for (Vector3fImmutable patrolPoint : building.getPatrolPoints()) { + writer.putVector3f(patrolPoint); + } + } else { + writer.putInt(0); + } + writer.putInt(0); //Sentry Point + + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) { + writer.putInt(1); //Tab left Random Town? //Opens up 16 Bytes + writer.putInt(4); + writer.putInt(0); + writer.putInt(0); + writer.putInt(4); + } else + writer.putInt(0); + writer.putInt(0); //array with size 32 bytes. // Adds information of building + } + writer.putInt(0); //ARRAY + writer.putInt(0); + } + + if (this.actionType == 18) { + Zone zone = asset.getParentZone(); + + if (zone == null) + return; + + City banedCity = City.getCity(zone.getPlayerCityUUID()); + + if (banedCity == null) + return; + + Bane bane = banedCity.getBane(); + + if (bane == null) + return; + + Guild attackerGuild = bane.getOwner().getGuild(); + + if (attackerGuild == null) + return; + + writer.putInt(asset.getObjectType().ordinal()); + writer.putInt(asset.getObjectUUID()); + + writer.putInt(0); + writer.putString(attackerGuild.getName()); + writer.putString(Guild.GetGL(attackerGuild).getName()); + writer.putInt(bane.getSiegePhase().ordinal()); //1 challenge //2 standoff //3 war + writer.put((byte) 0); + + if (!bane.isAccepted() && this.assetManager.getGuild() == banedCity.getGuild() && GuildStatusController.isInnerCouncil(this.assetManager.getGuildStatus())) + writer.put((byte) 1); //canSetTime + else + writer.put((byte) 0); + + DateTime placedOn = bane.getLiveDate(); + + if (placedOn == null) + placedOn = new DateTime(DateTime.now()); + + //set Calander to date of bane live. + DateTime now = DateTime.now(); + DateTime defaultTime = new DateTime(bane.getPlacementDate()); + DateTime playerEnterWorldTime = new DateTime(this.assetManager.getTimeStamp("EnterWorld")); + Period period = new Period(playerEnterWorldTime.getMillis(), now.getMillis()); + int hoursLoggedIn = period.getHours(); + hoursLoggedIn = hoursLoggedIn < 0 ? 0 : hoursLoggedIn; + + defaultTime = defaultTime.plusDays(2); + defaultTime = defaultTime.hourOfDay().setCopy(22); + defaultTime = defaultTime.minuteOfHour().setCopy(0); + defaultTime = defaultTime.secondOfMinute().setCopy(0); + + long curTime = now.getMillis(); + long timeLeft = 0; + + if (bane.getLiveDate() != null) + timeLeft = bane.getLiveDate().getMillis() - curTime; + else + timeLeft = defaultTime.getMillis() - curTime + 1000; + + //DO not touch these. They are static formula's until i get the correct converter for SB Time. + + writer.put((byte) placedOn.dayOfMonth().get()); + writer.put((byte) placedOn.monthOfYear().get()); + writer.putInt(placedOn.year().get() - 1900); + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + + if (timeLeft < 0) + writer.putInt(0); + else + writer.putInt((int) timeLeft / 1000); // Time remaing until bane/Seconds + + if (attackerGuild.getGuildState() == GuildState.Sworn) + writer.putInt(4); //3 capture/errant,4 capture/sworn, 5 destroy/soveirgn. + else + writer.putInt(5); + + writer.put((byte) (16 - hoursLoggedIn)); // hour start + writer.put((byte) (24 - hoursLoggedIn)); // hour end + writer.put((byte) 2); + writer.putString(banedCity.getCityName()); + writer.putString(Guild.GetGL(bane.getOwner().getGuild()) != null ? Guild.GetGL(bane.getOwner().getGuild()).getName() : "No Guild Leader"); + GuildTag._serializeForDisplay(attackerGuild.getGuildTag(), writer); + GuildTag._serializeForDisplay(attackerGuild.getNation().getGuildTag(), writer); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + + if (this.actionType == 3) { + + writer.putInt(targetType); + writer.putInt(targetID); + + Guild nation = null; + Building building = BuildingManager.getBuildingFromCache(targetID); + Guild guild = building.getGuild(); + Zone zone = ZoneManager.findSmallestZone(building.getLoc()); + + writer.putInt(0);//unknown Might be to allow manager to open or not! + writer.putString(building.getName()); + + AbstractCharacter buildingOwner = building.getOwner(); + + if (buildingOwner == null) + writer.putString("Morloch"); + else + writer.putString(buildingOwner.getName()); + + if (zone == null) + writer.putString("Forlord"); + else + writer.putString(zone.getName()); + + writer.putString(building.getGuild().getName()); + + writer.putInt(building.getRank()); + + // Maintenance costs include resource if + // this structure is an R8 tree + + if (building.getRank() == 8) + writer.putInt(5); // Resources included + else + writer.putInt(1); // Gold only + + writer.putInt(2308551); //Gold + if (building.getBlueprint() == null) + writer.putInt(0); + else + writer.putInt(building.getBlueprint().getMaintCost(building.getRank())); // maint cost + + if (building.getRank() == 8) { + writer.putInt(74856115); // Stone + writer.putInt(1500); // maint cost + writer.putInt(-1603256692); // Lumber + writer.putInt(1500); // maint cost + writer.putInt(-1596311545); // Galvor + writer.putInt(5); // maint cost + writer.putInt(1532478436); // Wormwood + writer.putInt(5); // maint cost + } + + LocalDateTime maintDate = building.maintDateTime; + + if (maintDate == null) + maintDate = LocalDateTime.now(); + writer.putLocalDateTime(LocalDateTime.now()); // current time + + // utc offset? + writer.putInt((int) java.time.Duration.between(LocalDateTime.now(), maintDate).getSeconds()); // Seconds to maint date + + writer.putInt(building.getStrongboxValue()); + writer.putInt(building.reserve);//reserve Sets the buildings reserve display + writer.putInt(0);//prosperity under maintenance (wtf is prosperity?) + writer.putInt(10); + writer.putFloat((float) .1); + + if (this.buttonUpgrade == 1) { + if (building.getBlueprint() == null) + this.upgradeCost = Integer.MAX_VALUE; + else if (building.getRank() == building.getBlueprint().getMaxRank()) + this.upgradeCost = Integer.MAX_VALUE; + else + this.upgradeCost = building.getBlueprint().getRankCost(Math.min(building.getRank() + 1, 7)); + + writer.putInt(this.upgradeCost); + } else + writer.putInt(0); + + LocalDateTime uc = LocalDateTime.now(); + + if (building.getDateToUpgrade() != null) + uc = building.getDateToUpgrade(); + + long timeLeft = uc.atZone(ZoneId.systemDefault()) + .toInstant().toEpochMilli() - System.currentTimeMillis(); + long hour = timeLeft / 3600000; + long noHour = timeLeft - (hour * 3600000); + long minute = noHour / 60000; + long noMinute = noHour - (minute * 60000); + long second = noMinute / 1000; + + writer.put((byte) 0);//Has to do with repair time. A 1 here puts 23.9 hours in repair time A 2 here is 1.9 days + writer.put((byte) 0);//unknown + writer.putInt(0); //unknown + + if (LocalDateTime.now().isAfter(uc)) { + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + } else { + writer.put((byte) (hour)); + writer.put((byte) minute); + writer.put((byte) second); + } + + if (timeLeft < 0) + writer.putInt(0); + else + writer.putInt((int) timeLeft); + + writer.putInt((int) building.getCurrentHitpoints()); + writer.putInt((int) building.getMaxHitPoints()); + writer.putInt(BuildingManager.GetRepairCost(building));//sets the repair cost. + writer.putInt(0);//unknown + + if (building.getBlueprint() == null) + writer.putInt(0); + else + writer.putInt(building.getBlueprint().getSlotsForRank(building.getRank())); + writer.put((byte) 1);//Has to do with removing update timer and putting in cost for upgrade + + writer.put(labelProtected); // 1 sets protection to invulnerable. + writer.put(labelSiege);// 1 sets the protection under siege + writer.put(labelCeaseFire); //0 with 1 set above sets to under siege // 1 with 1 set above sets protection status to under siege(cease fire) + + writer.put(buttonTransfer);// 1 enables the transfer asset button + writer.put(buttonDestroy);// 1 enables the destroy asset button + writer.put(buttonAbandon);// 1 here enables the abandon asset button + writer.put(buttonUpgrade); //disable upgrade building + + if (building.getBlueprint() == null) + writer.putInt(0); + else + writer.putInt(building.getBlueprint().getIcon()); //Symbol + + if (guild == null) { + for (int i = 0; i < 3; i++) + writer.putInt(16); + for (int i = 0; i < 2; i++) + writer.putInt(0); + } else { + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + nation = guild.getNation(); + } + + if (nation == null) { + for (int i = 0; i < 3; i++) + writer.putInt(16); + for (int i = 0; i < 2; i++) + writer.putInt(0); + } else { + GuildTag._serializeForDisplay(nation.getGuildTag(), writer); + } + writer.putInt(0); + writer.putInt(0); + writer.putInt(0);//1 makes it so manage window does not open. + + if (!building.assetIsProtected() && !building.getProtectionState().equals(ProtectionState.PENDING)) { + writer.putInt(0); + } else { + writer.putInt(1); //kos on/off? + writer.putInt(3); // was 3 + if (zone.getPlayerCityUUID() != 0 && asset.assetIsProtected()) { + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(City.getCity(zone.getPlayerCityUUID()).getTOL().getObjectUUID()); + } else { + writer.putInt(0); + writer.putInt(0); + } + + writer.putInt(0); + writer.putInt(0); + + writer.putInt(targetType3); + writer.putInt(targetID3); + + + if (building.getProtectionState() == ProtectionState.PENDING) + writer.put((byte) 1); //Accept or decline. + else + writer.put((byte) 0); + + if (building.taxType == TaxType.NONE) + writer.put((byte) 0); //? ?? + else if (building.taxDateTime != null) + writer.put((byte) 1); + else + writer.put((byte) 0); + + writer.putString(""); //tree of life protection tax + writer.putInt(0); //?? + writer.putInt(0); //?? + if (building.taxType == TaxType.NONE) { + writer.putInt(0); + writer.putInt(0); + } else if (building.taxType == TaxType.WEEKLY) { + writer.putInt(building.taxAmount); + writer.putInt(0); + } else { + writer.putInt(0); + writer.putInt(building.taxAmount); + } + + + writer.put(building.enforceKOS ? (byte) 1 : 0); //enforceKOS + writer.put((byte) 0); //? + writer.putInt(1); + } + + + ConcurrentHashMap npcList = building.getHirelings(); + writer.putInt(npcList.size()); + if (npcList.size() > 0) { + for (AbstractCharacter npcHire : npcList.keySet()) { + writer.putInt(npcHire.getObjectType().ordinal()); + if (npcHire.getObjectType() == GameObjectType.Mob) + writer.putInt(((Mob) npcHire).getDBID()); + else + writer.putInt(npcHire.getObjectUUID()); + if (npcHire.getObjectType() == GameObjectType.NPC) { + writer.putString(((NPC) npcHire).getContract().getName()); + writer.putString(npcHire.getName()); + } else if (npcHire.getObjectType() == GameObjectType.Mob) { + writer.putString(((Mob) npcHire).getContract().getName()); + if (((Mob) npcHire).getNameOverride().length() > 0) { + writer.putString(((Mob) npcHire).getNameOverride()); + } else { + writer.putString(npcHire.getName()); + } + } else + writer.putString("Error: Nothing Here"); + writer.putInt(npcHire.getRank()); + writer.putInt(Blueprint.getNpcMaintCost(npcHire.getRank())); + if (npcHire.getObjectType() == GameObjectType.NPC) + writer.putInt(((NPC) npcHire).getContract().getIconID()); // Was 60 + else if (npcHire.getObjectType() == GameObjectType.Mob) + writer.putInt(((Mob) npcHire).getContract().getIconID()); // Was 60 + + int contractID = 0; + + + if (npcHire.getObjectType() == GameObjectType.Mob) + contractID = ((Mob) npcHire).getContract().getContractID(); + else if (npcHire.getObjectType() == GameObjectType.NPC) + contractID = ((NPC) npcHire).getContract().getContractID(); + + if (contractID == 830) { + writer.putInt(24580); + } else if (building.getBlueprint() != null && (building.getBlueprint().getBuildingGroup() == BuildingGroup.FORGE || building.getBlueprint().getBuildingGroup() == BuildingGroup.MAGICSHOP || building.getBlueprint().getBuildingGroup() == BuildingGroup.TAILOR)) { + + writer.put((byte) 0); + writer.put((byte) 4); + writer.put((byte) 128); + writer.put((byte) 0); + + } else { + writer.put((byte) 0); + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) + writer.put((byte) 1); + else + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + } + + + if (!npcHire.isAlive()) { + writer.put((byte) 1); // 1 SHOWs respawning + int respawnRemaining = (int) (((Mob) npcHire).deathTime + ((Mob) npcHire).spawnTime * 1000 - System.currentTimeMillis()) / 1000; + writer.putInt(respawnRemaining); // Seconds in respawn remaining. + writer.putInt(((Mob) npcHire).spawnTime); // max seconds for respawn + } else + writer.put((byte) 0); + + } + } + } + if (this.actionType == 4) { + writer.putInt(targetType); + writer.putInt(targetID); + Building building = BuildingManager.getBuildingFromCache(targetID); + + writer.putInt(preName01); + writer.putString(building.getName()); //assetName + writer.putString(building.getOwnerName()); //ownerName + writer.putString(building.getGuild().getName());//guild name + writer.putString(building.getCityName()); //City Name + writer.putInt(building.getRank()); + if (building.getBlueprint() == null) + writer.putInt(0); + else + writer.putInt(building.getBlueprint().getIcon()); + + //tags + GuildTag._serializeForDisplay(building.getGuild().getGuildTag(), writer); + GuildTag._serializeForDisplay(building.getGuild().getNation().getGuildTag(), writer); + + writer.putInt(unknown14); + writer.putInt(unknown15); + writer.putInt(unknown16); + writer.putInt(unknown17); + writer.putInt(0); // previously uninitialized unknown18 + } + } + + public int getRank() { + return rank; + } + + public void setRank(int rank) { + this.rank = rank; + } + + public int getSymbol() { + return symbol; + } + + public void setSymbol(int symbol) { + this.symbol = symbol; + } + + public int getUnknown04() { + return unknown04; + } + + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + public int getUnknown05() { + return unknown05; + } + + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + public int getUnknown06() { + return unknown06; + } + + public void setUnknown06(int unknown06) { + this.unknown06 = unknown06; + } + + public void setUnknown07(int unknown07) { + this.unknown07 = unknown07; + } + + public String getAssetName() { + return assetName; + } + + public void setAssetName(String AssetName) { + this.assetName = AssetName; + } + + public void setAssetName1(String AssetName1) { + this.AssetName1 = AssetName1; + } + + public String getCityName() { + return CityName; + } + + public void setUnknown54(int unknown54) { + this.unknown54 = unknown54; + } + + public int getStrongbox() { + return strongbox; + } + + public void setStrongbox(int strongbox) { + this.strongbox = strongbox; + } + + public int getBaneHour() { + return baneHour; + } + + public Building getAsset() { + return asset; + } + + public void setAsset(Building asset) { + this.asset = asset; + } + +} + +//Debug Info +//Run: Failed to make object TEMPLATE:135700 INSTANCE:1717987027141... (t=50.46) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027161... (t=50.46) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027177... (t=50.67) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:60040 INSTANCE:1717987027344... (t=50.87) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:3 INSTANCE:1717987027164... (t=50.88) (r=7/4/2011 11:56:39) + diff --git a/src/engine/net/client/msg/ManageNPCMsg.java b/src/engine/net/client/msg/ManageNPCMsg.java new file mode 100644 index 00000000..8772b7c0 --- /dev/null +++ b/src/engine/net/client/msg/ManageNPCMsg.java @@ -0,0 +1,804 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.Enum.MinionType; +import engine.Enum.ProtectionState; +import engine.gameManager.NPCManager; +import engine.gameManager.PowersManager; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; +import engine.powers.EffectsBase; +import org.joda.time.DateTime; +import org.joda.time.Period; +import org.joda.time.Seconds; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Order NPC + */ +public class ManageNPCMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + private int unknown03; + private int unknown04; + private int unknown05; + private int unknown06; + private int messageType; + private int unknown01; + private int buildingID; + private int unknown20; + private int unknown26; + private int unknown83; + + /** + * This is the general purpose constructor + */ + public ManageNPCMsg(AbstractCharacter ac) { + super(Protocol.MANAGENPC); + this.targetType = ac.getObjectType().ordinal(); + this.targetID = ac.getObjectUUID(); + this.messageType = 1; //This seems to be the "update Hireling window" value flag + + //Unknown defaults... + + this.unknown20 = 0; + this.unknown26 = 0; + this.unknown83 = 0; + } + + //Serialize lists for Bulwarks + + private static void serializeBulwarkList(ByteBufferWriter writer, int minion) { + + writer.putInt(0); + + for (int i = 0; i < 3; i++) + writer.putInt(0); //static + + writer.putInt(9); + writer.putInt(5); + writer.putInt(9); + writer.putInt(5); + writer.put((byte) 0); + + writer.putInt((minion == 1) ? 900 : 600); //roll time + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); //Array + writer.put((byte) 0); + + if (minion == 1) + writer.putString("Trebuchet"); + else if (minion == 2) + writer.putString("Ballista"); + else + writer.putString("Mangonel"); + + writer.put((byte) 1); + writer.putString("A weapon suited to laying siege"); + } + + private static void serializeGuardList(ByteBufferWriter writer, int minion) { + + writer.putInt(1); + + for (int i = 0; i < 3; i++) + writer.putInt(0); //static + + writer.putInt(minion); + writer.putInt(1); + writer.putInt(minion); + writer.putInt(1); + writer.put((byte) 0); + + writer.putInt(600); //roll time + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); //Array + writer.put((byte) 0); + + MinionType minionType = MinionType.ContractToMinionMap.get(minion); + writer.putString(minionType != null ? minionType.getRace() + " " + minionType.getName() : "Minion Guard"); + writer.put((byte) 1); + writer.putString("A Guard To Protect Your City."); + } + + @Override + protected int getPowerOfTwoBufferSize() { + return (19); // 2^10 == 1024 + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //TODO do we need to do anything here? Does the client ever send this message to the server? + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + Period upgradePeriod; + int upgradePeriodInSeconds; + + try { + + + writer.putInt(messageType); //1 + if (messageType == 5) { + writer.putInt(unknown20);//0 + writer.putInt(targetType); + writer.putInt(targetID); + + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(buildingID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + } else if (messageType == 1) { + NPC npc = null; + Mob mobA = null; + + if (this.targetType == GameObjectType.NPC.ordinal()) { + + npc = NPC.getFromCache(this.targetID); + + if (npc == null) { + Logger.error("Missing NPC of ID " + this.targetID); + return; + } + + Contract contract = null; + contract = npc.getContract(); + + if (contract == null) { + Logger.error("Missing contract for NPC " + this.targetID); + return; + } + + writer.putInt(0); //anything other than 0 seems to mess up the client + writer.putInt(targetType); + writer.putInt(targetID); + writer.putInt(0); //static.... + writer.putInt(0);//static.... + writer.putInt(Blueprint.getNpcMaintCost(npc.getRank())); // salary + + writer.putInt(npc.getUpgradeCost()); + + if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now())) + upgradePeriod = new Period(DateTime.now(), npc.getUpgradeDateTime()); + else + upgradePeriod = new Period(0); + + writer.put((byte) upgradePeriod.getDays()); //for timer + writer.put((byte) unknown26);//unknown + writer.putInt(100); //unknown + + writer.put((byte) upgradePeriod.getHours()); //for timer + writer.put((byte) upgradePeriod.getMinutes()); //for timer + writer.put((byte) upgradePeriod.getSeconds()); //for timer + + if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now())) + upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), npc.getUpgradeDateTime()).getSeconds(); + else + upgradePeriodInSeconds = 0; + + writer.putInt(upgradePeriodInSeconds); + + writer.put((byte) 0); + writer.put((byte) (npc.getRank() == 7 ? 0 : 1)); //0 will make the upgrade field show "N/A" + writer.put((byte) 0); + writer.put((byte) 0); + writer.putInt(0); + writer.putInt(10000); //no idea... + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + writer.putInt(0); + + NPCProfits profit = NPC.GetNPCProfits(npc); + + if (profit == null) + profit = NPCProfits.defaultProfits; + //adding .000000001 to match client. + int buyNormal = (int) ((profit.buyNormal + .000001f) * 100); + int buyGuild = (int) ((profit.buyGuild + .000001f) * 100); + int buyNation = (int) ((profit.buyNation + .000001f) * 100); + + int sellNormal = (int) ((profit.sellNormal + .000001f) * 100); + int sellGuild = (int) ((profit.sellGuild + .000001f) * 100); + int sellNation = (int) ((profit.sellNation + .000001f) * 100); + + writer.putInt(buyNormal); + writer.putInt(buyGuild); + writer.putInt(buyNation); + writer.putInt(sellNormal); + writer.putInt(sellGuild); + writer.putInt(sellNation); + + if (contract.isRuneMaster()) { + writer.putInt(0); //vendor slots + writer.putInt(0); //artillery slots + + //figure out number of protection slots based on building rank + int runemasterSlots = (2 * npc.getRank()) + 6; + + writer.putInt(runemasterSlots); + + for (int i = 0; i < 13; i++) { + writer.putInt(0); //statics + } + //some unknown list + writer.putInt(4); //list count + writer.putInt(17); + writer.putInt(2); + writer.putInt(12); + writer.putInt(23); + + writer.putInt(0); //static + writer.putInt(0); //static + + //TODO add runemaster list here + + ArrayList buildingList = NPCManager.getProtectedBuildings(npc); + + writer.putInt(buildingList.size()); + + for (Building b : buildingList) { + writer.putInt(3); + writer.putInt(b.getObjectType().ordinal()); + writer.putInt(b.getObjectUUID()); + + writer.putInt(npc.getParentZone().getObjectType().ordinal()); + writer.putInt(npc.getParentZone().getObjectUUID()); + + writer.putLong(0); //TODO Identify what Comp this is suppose to be. + if (b.getProtectionState() == ProtectionState.PENDING) + writer.put((byte) 1); + else + writer.put((byte) 0); + writer.put((byte) 0); + writer.putString(b.getName()); + writer.putInt(1);//what? + writer.putInt(1);//what? + //taxType = b.getTaxType() + switch (b.taxType) { + case NONE: + writer.putInt(0); + writer.putInt(0); + break; + case WEEKLY: + writer.putInt(b.taxAmount); + writer.putInt(0); + break; + case PROFIT: + writer.putInt(0); + writer.putInt(b.taxAmount); + break; + + } + writer.put(b.enforceKOS ? (byte) 1 : 0); //ENFORCE KOS + writer.put((byte) 0); //?? + writer.putInt(1); + } + + writer.putInt(0); //artillery captain list + + } else if (contract.isArtilleryCaptain()) { + int slots = 1; + if (contract.getContractID() == 839) + slots = 3; + + + writer.putInt(0); //vendor slots + writer.putInt(slots); //artillery slots + writer.putInt(0); //runemaster slots + + for (int i = 0; i < 13; i++) { + writer.putInt(0); //statics + } + //some unknown list + writer.putInt(1); //list count + writer.putInt(16); + + writer.putInt(0); //static + writer.putInt(0); //static + writer.putInt(0); //runemaster list + + //artillery captain list + ConcurrentHashMap siegeMinions = npc.getSiegeMinionMap(); + writer.putInt(1 + siegeMinions.size()); + serializeBulwarkList(writer, 1); //Trebuchet + //serializeBulwarkList(writer, 2); //Ballista + + if (siegeMinions != null && siegeMinions.size() > 0) + + for (Mob mob : siegeMinions.keySet()) { + this.unknown83 = mob.getObjectUUID(); + writer.putInt(2); + writer.putInt(mob.getObjectType().ordinal()); + writer.putInt(this.unknown83); + writer.putInt(0); + writer.putInt(10); + writer.putInt(0); + writer.putInt(1); + writer.putInt(1); + + long curTime = System.currentTimeMillis() / 1000; + long upgradeTime = (mob.deathTime + (mob.spawnTime * 1000)) / 1000; + long timeLife = upgradeTime - curTime; + + if (upgradeTime * 1000 > System.currentTimeMillis()) { + if (mob.npcOwner.isAlive()) { + writer.put((byte) 0);//shows respawning timer + writer.putInt(mob.spawnTime); + writer.putInt(mob.spawnTime); + writer.putInt((int) timeLife); //time remaining for mob that is dead + writer.putInt(0); + writer.put((byte) 0); + writer.putString(mob.getNameOverride().isEmpty() ? mob.getName() : mob.getNameOverride()); + writer.put((byte) 0); + } else { + writer.put((byte) 0);//shows respawning timer + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); //time remaining for mob that is dead + writer.putInt(0); + writer.put((byte) 0); + writer.putString(mob.getNameOverride().isEmpty() ? mob.getName() : mob.getNameOverride()); + writer.put((byte) 0); + } + } else { + //nothing required for countdown for a mob that is alive + writer.put((byte) 1);//shows "Standing By" + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + writer.putString(mob.getNameOverride().isEmpty() ? mob.getName() : mob.getNameOverride()); + writer.put((byte) 0); + } + } + return; + + } else { + + if (npc.getCanRoll().isEmpty()) + writer.putInt(0); + else + writer.putInt(npc.getRank()); //vendor slots + + writer.putInt(0); //artilerist slots + writer.putInt(0); //runemaster slots + + writer.putInt(1); //is this static? + + for (int i = 0; i < 4; i++) + writer.putInt(0); //statics + + HashSet rollableSet = npc.getCanRoll(); + + //Begin Item list for creation. + writer.putInt(rollableSet.size()); + + for (int ib : rollableSet) { + ItemBase item = ItemBase.getItemBase(ib); + writer.put((byte) 1); + writer.putInt(0); + writer.putInt(ib); //itemID + writer.putInt(item.getBaseValue()); + writer.putInt(600); + writer.put((byte) 1); + writer.put((byte) item.getModTable()); + writer.put((byte) item.getModTable()); + writer.put((byte) item.getModTable()); + writer.put((byte) item.getModTable());//EffectItemType + } + + List itemList = npc.getRolling(); + itemList = itemList.stream().limit(npc.getRank()).collect(Collectors.toList()); + + writer.putInt(itemList.size()); + + for (Item i : itemList) { + + ItemBase ib = i.getItemBase(); + + writer.put((byte) 0); // ? Unknown45 + writer.putInt(i.getObjectType().ordinal()); + writer.putInt(i.getObjectUUID()); + + writer.putInt(0); + writer.putInt(i.getItemBaseID()); + writer.putInt(ib.getBaseValue()); + + long timeLife = i.getDateToUpgrade() - System.currentTimeMillis(); + + timeLife /= 1000; + writer.putInt((int) timeLife); + writer.putInt(npc.getRollingTimeInSeconds(i.getItemBaseID())); + writer.putInt(1); + + if (i.isComplete()) + writer.put((byte) 1); + else + writer.put((byte) 0); + + ArrayList effectsList = i.getEffectNames(); + EffectsBase prefix = null; + EffectsBase suffix = null; + + for (String effectName : effectsList) { + + if (effectName.contains("PRE")) + prefix = PowersManager.getEffectByIDString(effectName); + + if (effectName.contains("SUF")) + suffix = PowersManager.getEffectByIDString(effectName); + } + + if ((prefix == null && suffix == null)) + writer.putInt(0); + else + writer.putInt(-1497023830); + + if ((prefix != null && !i.isRandom()) || (prefix != null && i.isComplete())) + writer.putInt(prefix.getToken()); + else + writer.putInt(0); + + if ((suffix != null && !i.isRandom()) || (suffix != null && i.isComplete())) + writer.putInt(suffix.getToken()); + else + writer.putInt(0); + + writer.putString(i.getCustomName()); + } + + + writer.putInt(0); + writer.putInt(0); + writer.putInt(1); + writer.putInt(0); + writer.putInt(3); + writer.putInt(3); + writer.putInt(0); + writer.putString("Repair items"); + writer.putString("percent"); + writer.putInt(npc.getRepairCost()); //cost for repair + writer.putInt(0); + + ArrayList modPrefixList = npc.getModTypeTable(); + Integer mod = modPrefixList.get(0); + + if (mod != 0) { + writer.putInt(npc.getModTypeTable().size()); //Effects size + + for (Integer mtp : npc.getModTypeTable()) { + + int imt = modPrefixList.indexOf(mtp); + writer.putInt(npc.getItemModTable().get(imt)); //? + writer.putInt(0); + writer.putInt(0); + writer.putFloat(2); + writer.putInt(0); + writer.putInt(1); + writer.putInt(2); + writer.putInt(0); + writer.putInt(1); + writer.put(npc.getItemModTable().get(imt)); + writer.put(npc.getItemModTable().get(imt)); + writer.put(npc.getItemModTable().get(imt)); + writer.put(npc.getItemModTable().get(imt));//writer.putInt(-916801465); effectItemType + writer.putInt(mtp); //prefix + int mts = modPrefixList.indexOf(mtp); + writer.putInt(npc.getModSuffixTable().get(mts)); //suffix + } + } else + writer.putInt(0); + + ArrayList inventory = npc.getInventory(); + writer.putInt(inventory.size()); //placeholder for item cnt + + for (Item i : inventory) + Item.serializeForClientMsgWithoutSlot(i, writer); + + writer.putInt(0); + writer.putInt(5); + writer.putInt(1); + writer.putInt(2); + writer.putInt(15); + writer.putInt(3); + writer.putInt(18); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + + } else if (this.targetType == GameObjectType.Mob.ordinal()) { + + mobA = Mob.getFromCacheDBID(this.targetID); + + if (mobA == null) { + Logger.error("Missing Mob of ID " + this.targetID); + return; + } + + if (mobA != null) { + + Contract con = mobA.getContract(); + + if (con == null) { + Logger.error("Missing contract for NPC " + this.targetID); + return; + } + + int maxSlots = 1; + + switch (mobA.getRank()) { + case 1: + case 2: + maxSlots = 1; + break; + case 3: + maxSlots = 2; + break; + case 4: + case 5: + maxSlots = 3; + break; + case 6: + maxSlots = 4; + break; + case 7: + maxSlots = 5; + break; + default: + maxSlots = 1; + + } + + if (NPC.ISGuardCaptain(mobA.getContract().getContractID()) == false) + maxSlots = 0; + + writer.putInt(0); //anything other than 0 seems to mess up the client + writer.putInt(targetType); + writer.putInt(targetID); + writer.putInt(0); //static.... + writer.putInt(0);//static.... + writer.putInt(Blueprint.getNpcMaintCost(mobA.getRank())); // salary + + writer.putInt(Mob.getUpgradeCost(mobA)); + + if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now())) + upgradePeriod = new Period(DateTime.now(), mobA.getUpgradeDateTime()); + else + upgradePeriod = new Period(0); + + writer.put((byte) upgradePeriod.getDays()); //for timer + writer.put((byte) unknown26);//unknown + writer.putInt(100); //unknown + + writer.put((byte) upgradePeriod.getHours()); //for timer + writer.put((byte) upgradePeriod.getMinutes()); //for timer + writer.put((byte) upgradePeriod.getSeconds()); //for timer + + if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now())) + upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), mobA.getUpgradeDateTime()).getSeconds(); + else + upgradePeriodInSeconds = 0; + + writer.putInt(upgradePeriodInSeconds); + + writer.put((byte) 0); + writer.put((byte) (mobA.getRank() == 7 ? 0 : 1)); //0 will make the upgrade field show "N/A" + writer.put((byte) 0); + writer.put((byte) 0); + writer.putInt(0); + writer.putInt(10000); //no idea... + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + writer.putInt(0); + + NPCProfits profit = NPCProfits.defaultProfits; + + writer.putInt((int) (profit.buyNormal * 100)); + writer.putInt((int) (profit.buyGuild * 100)); + writer.putInt((int) (profit.buyNation * 100)); + writer.putInt((int) (profit.sellNormal * 100)); + writer.putInt((int) (profit.sellGuild * 100)); + writer.putInt((int) (profit.sellNation * 100)); + + writer.putInt(0); //vendor slots + writer.putInt(maxSlots); //artillery slots + writer.putInt(0); //runemaster slots + + for (int i = 0; i < 13; i++) + writer.putInt(0); //statics + + //some unknown list + writer.putInt(1); //list count + writer.putInt(16); + + writer.putInt(0); //static + writer.putInt(0); //static + writer.putInt(0); //runemaster list + + //artillery captain list + ConcurrentHashMap siegeMinions = mobA.getSiegeMinionMap(); + + writer.putInt(siegeMinions.size() + 1); + serializeGuardList(writer, mobA.getContract().getContractID()); //Guard + + if (siegeMinions != null && siegeMinions.size() > 0) + + for (Mob mob : siegeMinions.keySet()) { + this.unknown83 = mob.getObjectUUID(); + writer.putInt(2); + writer.putInt(mob.getObjectType().ordinal()); + writer.putInt(this.unknown83); + writer.putInt(0); + writer.putInt(10); + writer.putInt(0); + writer.putInt(1); + writer.putInt(1); + + long curTime = System.currentTimeMillis() / 1000; + long upgradeTime = (mob.deathTime + (mob.spawnTime * 1000)) / 1000; + long timeLife = upgradeTime - curTime; + + if (upgradeTime * 1000 > System.currentTimeMillis()) { + if (mob.npcOwner.isAlive()) { + writer.put((byte) 0);//shows respawning timer + writer.putInt(mob.spawnTime); + writer.putInt(mob.spawnTime); + writer.putInt((int) timeLife); //time remaining for mob that is dead + writer.putInt(0); + writer.put((byte) 0); + writer.putString(mob.getNameOverride().isEmpty() ? mob.getName() : mob.getNameOverride()); + writer.put((byte) 0); + } else { + writer.put((byte) 0);//shows respawning timer + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); //time remaining for mob that is dead + writer.putInt(0); + writer.put((byte) 0); + writer.putString(mob.getNameOverride().isEmpty() ? mob.getName() : mob.getNameOverride()); + writer.put((byte) 0); + } + } else { + //nothing required for countdown for a mob that is alive + writer.put((byte) 1);//shows "Standing By" + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + writer.putString(mob.getNameOverride().isEmpty() ? mob.getName() : mob.getNameOverride()); + writer.put((byte) 0); + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public int getUnknown01() { + return unknown01; + } + + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + public int getUnknown03() { + return unknown03; + } + + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + public int getUnknown04() { + return unknown04; + } + + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + public int getUnknown05() { + return unknown05; + } + + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + public int getUnknown06() { + return unknown06; + } + + public void setUnknown06(int unknown06) { + this.unknown06 = unknown06; + } + public int getMessageType() { + return messageType; + } + + public void setMessageType(int messageType) { + this.messageType = messageType; + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + +} diff --git a/src/engine/net/client/msg/MerchantMsg.java b/src/engine/net/client/msg/MerchantMsg.java new file mode 100644 index 00000000..d14d0692 --- /dev/null +++ b/src/engine/net/client/msg/MerchantMsg.java @@ -0,0 +1,228 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class MerchantMsg extends ClientNetMsg { + + private int type; + private int unknown01; + private int unknown02; + private int unknown03; + private int npcType; + private int npcID; + private int cityType; + private int cityID; + private int teleportTime; + private int unknown04; + private int itemType; + private int itemID; + private int amount; + private int hashID; + + /** + * This is the general purpose constructor. + */ + public MerchantMsg() { + super(Protocol.MERCHANT); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public MerchantMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MERCHANT, origin, reader); + } + + /** + * Copy constructor + */ + public MerchantMsg(MerchantMsg msg) { + super(Protocol.MERCHANT); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + if (this.type == 11 || type == 13) { + this.cityType = reader.getInt(); + this.cityID = reader.getInt(); + this.teleportTime = reader.getInt(); + } else if (this.type == 18) { + this.itemType = reader.getInt(); + this.itemID = reader.getInt(); + this.amount = reader.getInt(); + } else if (this.type == 17) { + this.hashID = reader.getInt(); + this.amount = reader.getInt(); + } else if (this.type == 19) { + this.hashID = reader.getInt(); + } else { + + this.cityType = 0; + this.cityID = 0; + this.teleportTime = 0; + } + this.unknown04 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.type); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.npcType); + writer.putInt(this.npcID); + if (this.type == 11 || type == 13) { + writer.putInt(this.cityType); + writer.putInt(this.cityID); + writer.putInt(this.teleportTime); + } + writer.putInt(this.unknown04); + if (this.type == 5) { + writer.putInt(2097253); + writer.putInt(0); + } + + } + + public int getType() { + return this.type; + } + + public void setType(int value) { + this.type = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getUnknown03() { + return this.unknown03; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public int getNPCType() { + return this.npcType; + } + + public void setNPCType(int value) { + this.npcType = value; + } + + public int getNPCID() { + return this.npcID; + } + + public void setNPCID(int value) { + this.npcID = value; + } + + public int getCityType() { + return this.cityType; + } + + public void setCityType(int value) { + this.cityType = value; + } + + public int getCityID() { + return this.cityID; + } + + public void setCityID(int value) { + this.cityID = value; + } + + public int getTeleportTime() { + return this.teleportTime; + } + + public void setTeleportTime(int value) { + this.teleportTime = value; + } + + public int getUnknown04() { + return this.unknown04; + } + + public void setUnknown04(int value) { + this.unknown04 = value; + } + + public int getItemType() { + return itemType; + } + + public void setItemType(int itemType) { + this.itemType = itemType; + } + + public int getItemID() { + return itemID; + } + + public void setItemID(int itemID) { + this.itemID = itemID; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + public int getHashID() { + return hashID; + } + + public void setHashID(int hashID) { + this.hashID = hashID; + } +} diff --git a/src/engine/net/client/msg/MinionTrainingMessage.java b/src/engine/net/client/msg/MinionTrainingMessage.java new file mode 100644 index 00000000..507db953 --- /dev/null +++ b/src/engine/net/client/msg/MinionTrainingMessage.java @@ -0,0 +1,201 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class MinionTrainingMessage extends ClientNetMsg { + private int npcID; + private int npcType; + private int buildingID; + private int buildingType; + private int type; + private int pad = 0; + private int objectType; + private int objectUUID; + private boolean isTreb = false; + private boolean isMangonal = false; + private boolean isBallista = false; + private int minion; + private int mobType; + private int mobID; + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public MinionTrainingMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MINIONTRAINING, origin, reader); + } + + public MinionTrainingMessage() { + super(Protocol.MINIONTRAINING); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + if (this.type == 2) { + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.objectType = reader.getInt(); + this.objectUUID = reader.getInt(); + reader.getInt(); + reader.getInt(); + + } else { + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + reader.getInt(); + this.minion = reader.getInt(); + if (this.minion == 1) + this.isTreb = true; + else if (this.minion == 2) + this.isBallista = true; + else if (this.minion == 3) + this.isMangonal = true; + reader.getInt(); + reader.getInt(); + } + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getUUID() { + return objectUUID; + + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public boolean isTreb() { + return isTreb; + } + + public void setTreb(boolean isTreb) { + this.isTreb = isTreb; + } + + public boolean isMangonal() { + return isMangonal; + } + + public void setMangonal(boolean isMangonal) { + this.isMangonal = isMangonal; + } + + public boolean isBallista() { + return isBallista; + } + + public void setBallista(boolean isBallista) { + this.isBallista = isBallista; + } + + public int getMinion() { + return minion; + } + + public void setMinion(int minion) { + this.minion = minion; + } + + public int getNpcID() { + return npcID; + } + + public void setNpcID(int npcID) { + this.npcID = npcID; + } + + public int getNpcType() { + return npcType; + } + + public void setNpcType(int npcType) { + this.npcType = npcType; + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public int getBuildingType() { + return buildingType; + } + + public void setBuildingType(int buildingType) { + this.buildingType = buildingType; + } + + public int getMobType() { + return mobType; + } + + public void setMobType(int mobType) { + this.mobType = mobType; + } + + public int getMobID() { + return mobID; + } + + public void setMobID(int mobID) { + this.mobID = mobID; + } + +} diff --git a/src/engine/net/client/msg/ModifyCommitToTradeMsg.java b/src/engine/net/client/msg/ModifyCommitToTradeMsg.java new file mode 100644 index 00000000..dad76a06 --- /dev/null +++ b/src/engine/net/client/msg/ModifyCommitToTradeMsg.java @@ -0,0 +1,142 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Commit to trade + * + * @author Eighty + */ +public class ModifyCommitToTradeMsg extends ClientNetMsg { + + private int playerType; + private int playerID; + private int targetType; + private int targetID; + private byte commit1; + private byte commit2; + + /** + * This is the general purpose constructor + */ + public ModifyCommitToTradeMsg(AbstractGameObject player, AbstractGameObject target, byte commit1, byte commit2) { + super(Protocol.TRADECONFIRMSTATUS); + this.playerType = player.getObjectType().ordinal(); + this.playerID = player.getObjectUUID(); + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.commit1 = commit1; + this.commit2 = commit2; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ModifyCommitToTradeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRADECONFIRMSTATUS, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.playerType = reader.getInt(); + this.playerID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + commit1 = reader.get(); + commit2 = reader.get(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(playerType); + writer.putInt(playerID); + writer.putInt(targetType); + writer.putInt(targetID); + writer.put(commit1); + writer.put(commit2); + } + + + /** + * @return the commit1 + */ + public byte getCommit1() { + return commit1; + } + + /** + * @param commit1 the commit1 to set + */ + public void setCommit1(byte commit1) { + this.commit1 = commit1; + } + + /** + * @return the commit2 + */ + public byte getCommit2() { + return commit2; + } + + /** + * @param commit2 the commit2 to set + */ + public void setCommit2(byte commit2) { + this.commit2 = commit2; + } + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + +} diff --git a/src/engine/net/client/msg/ModifyHealthKillMsg.java b/src/engine/net/client/msg/ModifyHealthKillMsg.java new file mode 100644 index 00000000..e0de0498 --- /dev/null +++ b/src/engine/net/client/msg/ModifyHealthKillMsg.java @@ -0,0 +1,235 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; + +public class ModifyHealthKillMsg extends ClientNetMsg { + + private int trains; + private int unknownID; //effectID + private int sourceType; + private int sourceID; + private int targetType; + private int targetID; + private int unknown02 = 0; //1 heal, 0 hurt? + private int unknown03 = 0; //0=normalCast, 1to4=powerFailed, 5=targetIsImmune, 6=targetResisted + private int unknown04 = -1; + private int unknown05 = 0; + private byte unknownByte = (byte) 0; //0 + private int powerID; + private String powerName; + private float health; + private float healthMod; + private float mana; + private float manaMod; + private float stamina; + private float staminaMod; + + /** + * This is the general purpose constructor. + */ + + public ModifyHealthKillMsg(AbstractCharacter source, AbstractCharacter target, float healthMod, float manaMod, float staminaMod, int powerID, String powerName, int trains, int effectID) { + super(Protocol.POWERACTIONDDDIE); + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + } else { + this.sourceType = 0; + this.sourceID = 0; + } + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.health = target.getCurrentHitpoints(); + this.healthMod = healthMod; + this.mana = target.getMana(); + this.manaMod = manaMod; + this.stamina = target.getStamina(); + this.staminaMod = staminaMod; + } else { + this.targetType = 0; + this.targetID = 0; + this.health = 0; + this.healthMod = 0; + this.mana = 0; + this.manaMod = 0; + this.stamina = 0; + this.staminaMod = 0; + } + this.unknownID = effectID; + this.trains = trains; + this.powerID = powerID; + this.powerName = powerName; + + this.unknown02 = 0; + } + + //called for kills + public ModifyHealthKillMsg(AbstractCharacter source, AbstractCharacter target, int powerID, String powerName, int trains, int effectID) { + super(Protocol.POWERACTIONDDDIE); + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + } else { + this.sourceType = 0; + this.sourceID = 0; + } + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.mana = target.getMana(); + this.stamina = target.getStamina(); + } else { + this.targetType = 0; + this.targetID = 0; + this.mana = 0f; + this.stamina = 0f; + } + this.health = -50f; + this.healthMod = 0f; + this.manaMod = 0f; + this.staminaMod = 0f; + this.unknown02 = 0; + this.unknownID = effectID; + this.trains = trains; + this.powerID = powerID; + this.powerName = powerName; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ModifyHealthKillMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.POWERACTIONDDDIE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.trains); + writer.putInt(this.unknownID); + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + writer.put(this.unknownByte); + writer.putInt(this.powerID); + writer.putString(this.powerName); + writer.putFloat(this.health); + writer.putFloat(this.healthMod); + writer.putFloat(this.mana); + writer.putFloat(this.manaMod); + writer.putFloat(this.stamina); + writer.putFloat(this.staminaMod); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.trains = reader.getInt(); + this.unknownID = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknownByte = reader.get(); + this.powerID = reader.getInt(); + this.powerName = reader.getString(); + this.health = reader.getFloat(); + this.healthMod = reader.getFloat(); + this.mana = reader.getFloat(); + this.manaMod = reader.getFloat(); + this.stamina = reader.getFloat(); + this.staminaMod = reader.getFloat(); + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public float getHealthMod() { + return healthMod; + } + + public float getManaMod() { + return manaMod; + } + + public float getStaminaMod() { + return manaMod; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } +} diff --git a/src/engine/net/client/msg/ModifyHealthMsg.java b/src/engine/net/client/msg/ModifyHealthMsg.java new file mode 100644 index 00000000..39a9a94b --- /dev/null +++ b/src/engine/net/client/msg/ModifyHealthMsg.java @@ -0,0 +1,271 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.Building; + +public class ModifyHealthMsg extends ClientNetMsg { + + private int trains; + private int unknownID; //effectID + private int sourceType; + private int sourceID; + private int targetType; + private int targetID; + private int omitFromChat = 0; //1 heal, 0 hurt? + private int unknown03 = 0; //0=normalCast, 1to4=powerFailed, 5=targetIsImmune, 6=targetResisted + private int unknown04 = -1; + private int unknown05 = 0; + private byte unknownByte = (byte) 0; //0 + private int powerID; + private String powerName; + private float health; + private float healthMod; + private float mana; + private float manaMod; + private float stamina; + private float staminaMod; + + /** + * This is the general purpose constructor. + */ + public ModifyHealthMsg(AbstractCharacter source, Building target, float healthMod, float manaMod, float staminaMod, int powerID, String powerName, int trains, int effectID) { + super(Protocol.POWERACTIONDD); + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + } else { + this.sourceType = 0; + this.sourceID = 0; + } + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.health = target.getCurrentHitpoints(); + this.healthMod = healthMod; + this.mana = 0; + this.manaMod = 0; + this.stamina = 0; + this.staminaMod = 0; + } else { + this.targetType = 0; + this.targetID = 0; + this.health = 0; + this.healthMod = 0; + this.mana = 0; + this.manaMod = 0; + this.stamina = 0; + this.staminaMod = 0; + } + this.unknownID = effectID; + this.trains = trains; + this.powerID = powerID; + this.powerName = powerName; + + this.omitFromChat = 0; + } + + public ModifyHealthMsg(AbstractCharacter source, AbstractCharacter target, float healthMod, float manaMod, float staminaMod, int powerID, String powerName, int trains, int effectID) { + super(Protocol.POWERACTIONDD); + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + } else { + this.sourceType = 0; + this.sourceID = 0; + } + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.health = target.getCurrentHitpoints(); + this.healthMod = healthMod; + this.mana = target.getMana(); + this.manaMod = manaMod; + this.stamina = target.getStamina(); + this.staminaMod = staminaMod; + } else { + this.targetType = 0; + this.targetID = 0; + this.health = 0; + this.healthMod = 0; + this.mana = 0; + this.manaMod = 0; + this.stamina = 0; + this.staminaMod = 0; + } + this.unknownID = effectID; + this.trains = trains; + this.powerID = powerID; + this.powerName = powerName; + + this.omitFromChat = 0; + } + + //called for kills + public ModifyHealthMsg(AbstractCharacter source, AbstractCharacter target, int powerID, String powerName, int trains, int effectID) { + super(Protocol.POWERACTIONDD); + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + } else { + this.sourceType = 0; + this.sourceID = 0; + } + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.mana = target.getMana(); + this.stamina = target.getStamina(); + } else { + this.targetType = 0; + this.targetID = 0; + this.mana = 0f; + this.stamina = 0f; + } + this.health = -50f; + this.healthMod = 0f; + this.manaMod = 0f; + this.staminaMod = 0f; + this.omitFromChat = 0; + this.unknownID = effectID; + this.trains = trains; + this.powerID = powerID; + this.powerName = powerName; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ModifyHealthMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.POWERACTIONDD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.trains); + writer.putInt(this.unknownID); + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.omitFromChat); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + writer.put(this.unknownByte); + writer.putInt(this.powerID); + writer.putString(this.powerName); + writer.putFloat(this.health); + writer.putFloat(this.healthMod); + writer.putFloat(this.mana); + writer.putFloat(this.manaMod); + writer.putFloat(this.stamina); + writer.putFloat(this.staminaMod); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.trains = reader.getInt(); + this.unknownID = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.omitFromChat = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknownByte = reader.get(); + this.powerID = reader.getInt(); + this.powerName = reader.getString(); + this.health = reader.getFloat(); + this.healthMod = reader.getFloat(); + this.mana = reader.getFloat(); + this.manaMod = reader.getFloat(); + this.stamina = reader.getFloat(); + this.staminaMod = reader.getFloat(); + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public float getHealthMod() { + return healthMod; + } + + public float getManaMod() { + return manaMod; + } + + public float getStaminaMod() { + return manaMod; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public void setOmitFromChat(int value) { + this.omitFromChat = value; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } +} diff --git a/src/engine/net/client/msg/ModifyStatMsg.java b/src/engine/net/client/msg/ModifyStatMsg.java new file mode 100644 index 00000000..f01ff56c --- /dev/null +++ b/src/engine/net/client/msg/ModifyStatMsg.java @@ -0,0 +1,91 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ModifyStatMsg extends ClientNetMsg { + + private int amount; + private int type; + private int unknown01; + + /** + * This is the general purpose constructor. + */ + public ModifyStatMsg() { + super(Protocol.RAISEATTR); + } + + public ModifyStatMsg(int amount, int type, int unknown01) { + super(Protocol.RAISEATTR); + this.amount = amount; + this.type = type; + this.unknown01 = unknown01; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ModifyStatMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.RAISEATTR, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.amount); + writer.putInt(this.type); + writer.putInt(this.unknown01); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.amount = reader.getInt(); + this.type = reader.getInt(); + this.unknown01 = reader.getInt(); + } + + public int getAmount() { + return this.amount; + } + + public void setAmount(int value) { + this.amount = value; + } + + public int getType() { + return this.type; + } + + public void setType(int value) { + this.type = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } +} diff --git a/src/engine/net/client/msg/MoveCorrectionMsg.java b/src/engine/net/client/msg/MoveCorrectionMsg.java new file mode 100644 index 00000000..67618bd8 --- /dev/null +++ b/src/engine/net/client/msg/MoveCorrectionMsg.java @@ -0,0 +1,226 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.math.Vector3fImmutable; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; + +public class MoveCorrectionMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private float startLat; + private float startAlt; + private float startLon; + private float endLat; + private float endAlt; + private float endLon; + private int unknown01 = 2; + private int unknown02 = 0; + private int unknown03 = 0; + + /** + * This is the general purpose constructor. + */ + public MoveCorrectionMsg(AbstractCharacter ac, boolean teleport) { + super(Protocol.MOVECORRECTION); + this.sourceType = ac.getObjectType().ordinal(); + this.sourceID = ac.getObjectUUID(); + this.startLat = ac.getLoc().x; + this.startAlt = ac.getLoc().y; + this.startLon = ac.getLoc().z; + if (teleport) { + this.endLat = ac.getLoc().x; + this.endAlt = ac.getLoc().y; + this.endLon = ac.getLoc().z; + } else { + if (ac.isMoving()) { + this.endLat = ac.getEndLoc().x; + this.endAlt = ac.getEndLoc().y; + this.endLon = ac.getEndLoc().z; + } else { + this.endLat = ac.getLoc().x; + this.endAlt = ac.getLoc().y; + this.endLon = ac.getLoc().z; + } + } + + this.unknown01 = Float.floatToIntBits(ac.getAltitude()); + this.unknown02 = Float.floatToIntBits(ac.getAltitude()); + this.unknown03 = Float.floatToIntBits(ac.getAltitude()); + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public MoveCorrectionMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MOVECORRECTION, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + + writer.putFloat(this.startLat); + writer.putFloat(this.startAlt); + writer.putFloat(this.startLon); + + writer.putFloat(this.endLat); + writer.putFloat(this.endAlt); + writer.putFloat(this.endLon); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + + this.startLat = reader.getFloat(); + this.startAlt = reader.getFloat(); + this.startLon = reader.getFloat(); + + this.endLat = reader.getFloat(); + this.endAlt = reader.getFloat(); + this.endLon = reader.getFloat(); + + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public float getStartLat() { + return this.startLat; + } + + public void setStartLat(float value) { + this.startLat = value; + } + + public float getStartLon() { + return this.startLon; + } + + public void setStartLon(float value) { + this.startLon = value; + } + + public float getStartAlt() { + return this.startAlt; + } + + public void setStartAlt(float value) { + this.startAlt = value; + } + + public float getEndLat() { + return this.endLat; + } + + public void setEndLat(float value) { + this.endLat = value; + } + + public float getEndLon() { + return this.endLon; + } + + public void setEndLon(float value) { + this.endLon = value; + } + + public float getEndAlt() { + return this.endAlt; + } + + public void setEndAlt(float value) { + this.endAlt = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getUnknown02() { + return this.unknown01; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getUnknown03() { + return this.unknown01; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public void setStartCoord(Vector3fImmutable value) { + this.startLat = value.x; + this.startAlt = value.y; + this.startLon = value.z; + } + + public void setEndCoord(Vector3fImmutable value) { + this.endLat = value.x; + this.endAlt = value.y; + this.endLon = value.z; + } + + public void setPlayer(AbstractCharacter ac) { + this.sourceType = 85; + this.sourceID = ac.getObjectUUID(); + this.setStartCoord(ac.getLoc()); + this.setEndCoord(ac.getEndLoc()); + } +} diff --git a/src/engine/net/client/msg/MoveToPointMsg.java b/src/engine/net/client/msg/MoveToPointMsg.java new file mode 100644 index 00000000..4a6c08dd --- /dev/null +++ b/src/engine/net/client/msg/MoveToPointMsg.java @@ -0,0 +1,304 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.Building; + +public class MoveToPointMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private float startLat; + private float startLon; + private float startAlt; + private float endLat; + private float endLon; + private float endAlt; + private int targetType; + private int targetID; + private int inBuilding; // 0=true, -1=false 0/1/2 = floor you are on + private int unknown01; + private byte unknown02; + private byte unknown03; + + /** + * This is the general purpose constructor. + */ + public MoveToPointMsg() { + super(Protocol.MOVETOPOINT); + } + + + public MoveToPointMsg(MoveToPointMsg msg) { + super(Protocol.MOVETOPOINT); + this.sourceType = msg.sourceType; + this.sourceID = msg.sourceID; + this.startLat = msg.startLat; + this.startLon = msg.startLon; + this.startAlt = msg.startAlt; + this.endLat = msg.endLat; + this.endLon = msg.endLon; + this.endAlt = msg.endAlt; + this.targetType = msg.targetType; + this.targetID = msg.targetID; + this.inBuilding = msg.inBuilding; + this.unknown01 = msg.unknown01; + this.unknown02 = msg.unknown02; + this.unknown03 = msg.unknown03; + } + + //Moving Furniture out of building to unload properly. //for outside regions only + public MoveToPointMsg(Building building) { + super(Protocol.MOVETOPOINT); + this.sourceType = building.getObjectType().ordinal(); + this.sourceID = building.getObjectUUID(); + this.startLat = building.getLoc().x; + this.startLon = building.getLoc().z; + this.startAlt = building.getLoc().y; + this.endLat = building.getLoc().x; + this.endLon = building.getLoc().z; + this.endAlt = building.getLoc().y; + this.targetType = 0; + this.targetID = 0; + this.inBuilding = -1; + this.unknown01 = -1; + this.unknown02 = 0; + this.unknown03 = 0; + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public MoveToPointMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MOVETOPOINT, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + + writer.putFloat(this.startLat); + writer.putFloat(this.startAlt); + writer.putFloat(this.startLon); + + writer.putFloat(this.endLat); + writer.putFloat(this.endAlt); + writer.putFloat(this.endLon); + + writer.putInt(this.targetType); + writer.putInt(this.targetID); + + writer.putInt(this.inBuilding); + writer.putInt(this.unknown01); + + writer.put((byte) 0); + writer.put((byte) 0); + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + + this.startLat = reader.getFloat(); + this.startAlt = reader.getFloat(); + this.startLon = reader.getFloat(); + + this.endLat = reader.getFloat(); + this.endAlt = reader.getFloat(); + this.endLon = reader.getFloat(); + + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + + this.inBuilding = reader.getInt(); + this.unknown01 = reader.getInt(); + + this.unknown02 = reader.get(); + this.unknown03 = reader.get(); + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public float getStartLat() { + return this.startLat; + } + + public void setStartLat(float value) { + this.startLat = value; + } + + public float getStartLon() { + return this.startLon; + } + + public void setStartLon(float value) { + this.startLon = value; + } + + public float getStartAlt() { + return this.startAlt; + } + + public void setStartAlt(float value) { + this.startAlt = value; + } + + public float getEndLat() { + return this.endLat; + } + + public void setEndLat(float value) { + this.endLat = value; + } + + public float getEndLon() { + return this.endLon; + } + + public void setEndLon(float value) { + this.endLon = value; + } + + public float getEndAlt() { + return this.endAlt; + } + + public void setEndAlt(float value) { + this.endAlt = value; + } + + public int getTargetType() { + return this.targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return this.targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public int getInBuilding() { + return this.inBuilding; + } + + public void setInBuilding(int value) { + this.inBuilding = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public void setStartCoord(Vector3fImmutable value) { + this.startLat = value.x; + this.startAlt = value.y; + this.startLon = value.z; + } + + public void setEndCoord(Vector3fImmutable value) { + this.endLat = value.x; + this.endAlt = value.y; + this.endLon = value.z; + } + + public void clearTarget() { + this.targetType = 0; + this.targetID = 0; + } + + public void setPlayer(AbstractCharacter ac) { + this.sourceType = ac.getObjectType().ordinal(); + this.sourceID = ac.getObjectUUID(); + this.setStartCoord(ac.getLoc()); + this.setEndCoord(ac.getEndLoc()); + this.targetType = 0; + this.targetID = 0; + this.inBuilding = ac.getInBuilding(); + this.unknown01 = ac.getInFloorID(); + + } + + public void setTarget(AbstractCharacter ac, Building target) { + if (target == null) { + this.setStartCoord(ac.getLoc()); + this.setEndCoord(ac.getEndLoc()); + this.targetType = 0; + this.targetID = 0; + this.inBuilding = -1; + this.unknown01 = -1; + } else { + Vector3fImmutable convertLocStart = ZoneManager.convertWorldToLocal(target, ac.getLoc()); + Vector3fImmutable convertLocEnd = convertLocStart; + if (ac.isMoving()) + convertLocEnd = ZoneManager.convertWorldToLocal(target, ac.getEndLoc()); + + this.setStartCoord(convertLocStart); + this.setEndCoord(convertLocEnd); + this.targetType = GameObjectType.Building.ordinal(); + this.targetID = target.getObjectUUID(); + this.inBuilding = ac.getInBuilding(); + this.unknown01 = ac.getInFloorID(); + } + + } + + public int getUnknown03() { + return unknown03; + } + + public int getUnknown02() { + return unknown02; + } +} diff --git a/src/engine/net/client/msg/ObjectActionMsg.java b/src/engine/net/client/msg/ObjectActionMsg.java new file mode 100644 index 00000000..c3922440 --- /dev/null +++ b/src/engine/net/client/msg/ObjectActionMsg.java @@ -0,0 +1,117 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +import java.util.ArrayList; + + +/** + * Use item + * + * @author Eighty + */ +public class ObjectActionMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private ArrayList targetCompID; + + /** + * This is the general purpose constructor + */ + public ObjectActionMsg(int unknown01, int unknown02, ArrayList targetCompID) { + super(Protocol.OBJECTACTION); + this.unknown01 = unknown01; + this.unknown02 = unknown02; + this.targetCompID = targetCompID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ObjectActionMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.OBJECTACTION, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + if (targetCompID == null) + targetCompID = new ArrayList<>(); + unknown01 = reader.getInt(); + unknown02 = reader.getInt(); + for (int i = 0; i < unknown01; i++) + targetCompID.add(reader.getLong()); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putInt(unknown02); + for (int i = 0; i < unknown01; i++) + writer.putLong(targetCompID.get(i)); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the targetCompID + */ + public ArrayList getTargetCompID() { + return targetCompID; + } + + /** + * @param targetCompID the targetCompID to set + */ + public void setTargetCompID(ArrayList targetCompID) { + this.targetCompID = targetCompID; + } + +} diff --git a/src/engine/net/client/msg/OpenFriendsCondemnListMsg.java b/src/engine/net/client/msg/OpenFriendsCondemnListMsg.java new file mode 100644 index 00000000..f6eaefad --- /dev/null +++ b/src/engine/net/client/msg/OpenFriendsCondemnListMsg.java @@ -0,0 +1,928 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + +public class OpenFriendsCondemnListMsg extends ClientNetMsg { + + private int messageType; + private ArrayList characterList; + private int buildingType; + private int buildingID; + + private int playerType; + private int playerID; + private int guildID; + private int inviteType; + private ConcurrentHashMap friends; + private int removeFriendType; + private int removeFriendID; + private boolean reverseKOS; //TODO Rename this for to fit ReverseKOS/Activate/deactive Condemned. + private ConcurrentHashMap guildCondemned; + private int nationID; + + public OpenFriendsCondemnListMsg(int messageType, ConcurrentHashMap friends) { + super(Protocol.OPENFRIENDSCONDEMNLIST); + this.messageType = messageType; + this.friends = friends; + + } + + public OpenFriendsCondemnListMsg(int messageType, ConcurrentHashMap guildCondemned, boolean reverse) { + super(Protocol.OPENFRIENDSCONDEMNLIST); + this.messageType = messageType; + this.guildCondemned = guildCondemned; + this.reverseKOS = reverse; + } + + // clone + + public OpenFriendsCondemnListMsg(OpenFriendsCondemnListMsg openFriendsCondemnListMsg) { + super(Protocol.OPENFRIENDSCONDEMNLIST); + this.messageType = openFriendsCondemnListMsg.messageType; + this.guildCondemned = openFriendsCondemnListMsg.guildCondemned; + this.reverseKOS = openFriendsCondemnListMsg.reverseKOS; + this.playerType = openFriendsCondemnListMsg.playerType; + this.playerID = openFriendsCondemnListMsg.playerID; + this.inviteType = openFriendsCondemnListMsg.inviteType; + this.removeFriendID = openFriendsCondemnListMsg.removeFriendID; + this.removeFriendType = openFriendsCondemnListMsg.removeFriendType; + this.reverseKOS = openFriendsCondemnListMsg.reverseKOS; + this.nationID = openFriendsCondemnListMsg.nationID; + this.buildingType = openFriendsCondemnListMsg.buildingType; + this.buildingID = openFriendsCondemnListMsg.buildingID; + this.friends = openFriendsCondemnListMsg.friends; + this.characterList = openFriendsCondemnListMsg.characterList; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public OpenFriendsCondemnListMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.OPENFRIENDSCONDEMNLIST, origin, reader); // openFriendsCondemnList =1239809615 + characterList = new ArrayList<>(); + } + + public void configure() { + + // Pre-Cache all players and guild targets + + if (characterList == null) + return; + + for (Integer uuid : characterList) { + + PlayerCharacter player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, uuid); + + if (player == null) + continue; + + Guild guild = player.getGuild(); + + if (guild == null) + continue; + } + + } + + public void configureHeraldry(PlayerCharacter player) { + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.messageType); + + if (this.messageType == 2) { + this.showHeraldy(writer); + return; + } + + if (this.messageType == 4) { + this.writeAddHealrdy(writer); + return; + } + if (this.messageType == 26) { + showBuildingFriends(writer); + return; + } + + if (this.messageType == 12) { + this.showCondemnList(writer); + return; + } + + if (this.messageType == 15) { + this.removeCondemned(writer); + return; + } + if (this.messageType == 17) { + this.handleActivateCondemned(writer); + return; + } + + + writer.putInt(0); + writer.putInt(this.characterList.size()); + writer.putInt(this.characterList.size()); + + for (Integer uuid : characterList) { + + PlayerCharacter player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, uuid); + + if (player == null) + continue; + + writer.put((byte) 1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(1); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(player.getObjectUUID()); + Guild guild = player.getGuild(); + Guild nation = null; + + if (guild != null) { + writer.putInt(guild.getObjectType().ordinal()); + writer.putInt(guild.getObjectUUID()); + nation = guild.getNation(); + if (nation != null) { + writer.putInt(nation.getObjectType().ordinal()); + writer.putInt(nation.getObjectUUID()); + } else { + writer.putInt(0); + writer.putInt(0); + } + } else { + for (int i = 0; i < 4; i++) + writer.putInt(0); + } + writer.putShort((short) 0); + writer.put((byte) 0); + writer.putString(player.getFirstName()); + + if (guild != null) { + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + } else { + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + } + if (nation != null) { + GuildTag._serializeForDisplay(nation.getGuildTag(), writer); + } else { + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + } + if (guild != null) + writer.putString(guild.getName()); + else + writer.putString("[No Guild]"); + if (nation != null) + writer.putString(nation.getName()); + else + writer.putString("[No Nation]"); + writer.putInt(0); + } + } + + private void readHandleToItem(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); //object Type; + reader.getInt(); //objectID; + + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + } + + private void writeHandleToItem(ByteBufferWriter writer) { + } + + private void removeCondemned(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(playerType); + writer.putInt(playerID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(removeFriendType); + writer.putInt(removeFriendID); + writer.putInt(buildingType); + writer.putInt(buildingID); + writer.putInt(0); + writer.putInt(0); + writer.putShort((short) 0); + + } + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.messageType = reader.getInt(); //25 on open of friends list // 28 friends list // 11 open condemned list // 14 Open condemned selection of (indivual, guild, nation) + + if (this.messageType == 1) { + this.viewHealrdy(reader); + return; + } + + if (this.messageType == 4) { + this.readAddHealrdy(reader); + return; + } + + if (this.messageType == 6) { + this.readRemoveHeraldry(reader); + return; + } + if (this.messageType == 11) { + this.ackBuildingFriends(reader); + reader.get(); + return; + } + + if (this.messageType == 14) { + this.addGuildCondemn(reader); + return; + } + if (this.messageType == 15) { + this.removeFriendList(reader); + reader.get(); + return; + } + + if (this.messageType == 17) { + this.handleActivateCondemned(reader); + return; + } + + if (this.messageType == 18) { + this.handleCondemnErrant(reader); + return; + } + + + if (this.messageType == 19) { + this.handleKOS(reader); + return; + } + + if (this.messageType == 23) { + this.readHandleToItem(reader); + return; + } + if (this.messageType == 28) { + addFriendsList(reader); + return; + } + if (this.messageType == 30) { + removeFriendList(reader); + return; + } + if (this.messageType == 25) { + ackBuildingFriends(reader); + return; + } + + reader.getInt(); + int size = reader.getInt(); + reader.getInt(); //size again + for (int i = 0; i < size; i++) { + reader.get(); + reader.getInt(); //0 + reader.getInt(); //0 + reader.getInt(); //1 + reader.getInt(); //Player Type + int ID = reader.getInt(); //Player ID + reader.getLong(); //Guild ID + reader.getLong(); //Nation ID + reader.getShort(); + reader.get(); + reader.getString(); //name + for (int j = 0; j < 10; j++)////////The problem is in here its stops here!!!! + reader.getInt(); //Guild and Nation tags + reader.getString(); //guild name + reader.getString(); //nation name + reader.getInt(); + this.characterList.add(ID); + } + } + + private void readRemoveHeraldry(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.playerType = reader.getInt(); + this.playerID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + } + + private void handleActivateCondemned(ByteBufferReader reader) { + reader.getInt(); + this.removeFriendType = reader.getInt(); + this.removeFriendID = reader.getInt(); + reader.getInt(); + this.buildingID = reader.getInt(); + this.reverseKOS = reader.get() == 1 ? true : false; + reader.getInt(); + reader.getInt(); + reader.get(); + } + + private void handleCondemnErrant(ByteBufferReader reader) { + reader.getInt(); + this.removeFriendType = reader.getInt(); + this.removeFriendID = reader.getInt(); + reader.getInt(); + this.buildingID = reader.getInt(); + this.reverseKOS = reader.get() == 1 ? true : false; + reader.getInt(); + reader.getInt(); + reader.get(); + } + + private void handleActivateCondemned(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(removeFriendType); + writer.putInt(removeFriendID); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(buildingID); + writer.put(reverseKOS ? (byte) 1 : (byte) 0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + } + + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return 12; + } + + private void addGuildCondemn(ByteBufferReader reader) { + reader.getInt(); + this.inviteType = reader.getInt(); + reader.getInt(); + this.playerID = reader.getInt(); + reader.getInt(); + this.guildID = reader.getInt(); + reader.getInt(); + this.nationID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.buildingID = reader.getInt(); + reader.getLong(); + reader.getShort(); + + + } + + private void addFriendsList(ByteBufferReader reader) { + reader.getInt(); + this.inviteType = reader.getInt(); //7 individual, 8 guild, 9 guild IC + this.playerType = reader.getInt(); + this.playerID = reader.getInt(); + reader.getInt(); + this.guildID = reader.getInt(); + + reader.getLong(); //Nation, do we need this? + reader.getLong(); + reader.getLong(); + reader.getLong(); + reader.getInt(); + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.get(); + + } + + private void handleKOS(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.buildingID = reader.getInt(); + reader.get(); + byte reverse = reader.get(); + this.reverseKOS = reverse == 1 ? true : false; + reader.getInt(); + reader.getInt(); + + } + + private void removeFriendList(ByteBufferReader reader) { + reader.getInt(); + this.playerType = reader.getInt(); + this.playerID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.removeFriendType = reader.getInt(); + this.removeFriendID = reader.getInt(); + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + + } + + + public int getRemoveFriendID() { + return removeFriendID; + } + + private void showCondemnList(ByteBufferWriter writer) { + String name = ""; + PlayerCharacter pc = null; + Guild guild = null; + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + writer.put(reverseKOS ? (byte) 1 : 0); //Reverse? + + int listSize = this.guildCondemned.size(); + writer.putInt(listSize); + writer.putInt(listSize); + + for (Condemned condemned : this.guildCondemned.values()) { + + + writer.put((byte) 1); + + switch (condemned.getFriendType()) { + case 2: + PlayerCharacter playerCharacter = (PlayerCharacter) DbManager.getObject(engine.Enum.GameObjectType.PlayerCharacter, condemned.getPlayerUID()); + + + guild = playerCharacter.getGuild(); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(condemned.getPlayerUID()); + writer.putInt(condemned.getFriendType()); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(condemned.getPlayerUID()); + writer.putInt(0); + writer.putInt(0); + writer.putInt(GameObjectType.Guild.ordinal()); + if (guild != null) + writer.putInt(guild.getObjectUUID()); + else + writer.putInt(0); + writer.put(condemned.isActive() ? (byte) 1 : (byte) 0); + writer.put((byte) 0); + writer.put(condemned.isActive() ? (byte) 1 : (byte) 0); + + if (playerCharacter != null) + writer.putString(playerCharacter.getFirstName()); + else + writer.putInt(0); + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + if (guild != null) + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + else { + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + } + break; + case 4: + guild = Guild.getGuild(condemned.getGuildUID()); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(condemned.getGuildUID()); + writer.putInt(condemned.getFriendType()); + writer.putLong(0); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(condemned.getGuildUID()); + writer.putLong(0); + writer.put((byte) 0); + writer.put(condemned.isActive() ? (byte) 1 : (byte) 0); + writer.put((byte) 0); + if (guild != null) + writer.putString(guild.getName()); + else + writer.putInt(0); + + if (guild != null) + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + else + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + break; + case 5: + guild = Guild.getGuild(condemned.getGuildUID()); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(condemned.getGuildUID()); + writer.putInt(condemned.getFriendType()); + writer.putLong(0); + writer.putLong(0); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(condemned.getGuildUID()); + writer.put((byte) 0); + writer.put((byte) 0); + writer.put(condemned.isActive() ? (byte) 1 : (byte) 0); + if (guild != null) + writer.putString(guild.getName()); + else + writer.putInt(0); + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + if (guild != null) + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + else { + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + } + break; + } + + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + } + + private void showBuildingFriends(ByteBufferWriter writer) { + + + String name = ""; + PlayerCharacter pc = null; + Guild guild = null; + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + int listSize = this.friends.size(); + writer.putInt(listSize); + writer.putInt(listSize); + + for (BuildingFriends friend : this.friends.values()) { + pc = PlayerCharacter.getFromCache(friend.getPlayerUID()); + guild = Guild.getGuild(friend.getGuildUID()); + if (friend.getFriendType() == 7) { + if (pc != null) + name = pc.getCombinedName(); + } else if (guild != null) + name = guild.getName(); + writer.put((byte) 1); + if (friend.getFriendType() == 7) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(friend.getPlayerUID()); + } else { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(friend.getGuildUID()); + } + writer.putInt(friend.getFriendType()); + writer.putInt(0); + writer.putInt(0); + + if (guild != null) { + writer.putInt(guild.getObjectType().ordinal()); + writer.putInt(guild.getObjectUUID()); + + if (!guild.getNation().isEmptyGuild()) { + writer.putInt(guild.getNation().getObjectType().ordinal()); + writer.putInt(guild.getNation().getObjectUUID()); + } else { + writer.putInt(0); + writer.putInt(0); + } + } else { + writer.putLong(0); + writer.putLong(0); + } + writer.putShort((short) 0); + writer.put((byte) 0); + writer.putString(name); + if (guild != null) + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + else { + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + } + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + + } + + private void ackBuildingFriends(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.buildingType = reader.getInt(); + this.buildingID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + + } + + private void viewHealrdy(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + } + + private void showHeraldy(ByteBufferWriter writer) { + + PlayerCharacter player = ((ClientConnection) this.getOrigin()).getPlayerCharacter(); + writer.putInt(0); //error pop up msg + writer.putInt(0); + +// writer.putInt(0); + + HashMap heraldryMap = Heraldry.HeraldyMap.get(player.getObjectUUID()); + + //send empty list if no heraldry + if (heraldryMap == null || heraldryMap.isEmpty()) { + writer.putInt(0); + return; + } + + + writer.putInt(heraldryMap.size()); + + for (int characterID : heraldryMap.keySet()) { + AbstractCharacter heraldryCharacter = null; + int characterType = heraldryMap.get(characterID); + if (characterType == GameObjectType.PlayerCharacter.ordinal()) + heraldryCharacter = PlayerCharacter.getFromCache(characterID); + else if (characterType == GameObjectType.NPC.ordinal()) + heraldryCharacter = NPC.getFromCache(characterID); + else if (characterType == GameObjectType.Mob.ordinal()) + heraldryCharacter = Mob.getFromCache(characterID); + + if (heraldryCharacter == null) + this.showNullHeraldryCharacter(writer); + else { + writer.put((byte) 1); + writer.putInt(heraldryCharacter.getObjectType().ordinal()); + writer.putInt(heraldryCharacter.getObjectUUID()); + + writer.putInt(9); + writer.putInt(heraldryCharacter.getObjectType().ordinal()); + writer.putInt(heraldryCharacter.getObjectUUID()); + + if (heraldryCharacter.getGuild() != null) { + writer.putInt(heraldryCharacter.getGuild().getObjectType().ordinal()); + writer.putInt(heraldryCharacter.getGuild().getObjectUUID()); + + if (!heraldryCharacter.getGuild().getNation().isEmptyGuild()) { + writer.putInt(heraldryCharacter.getGuild().getNation().getObjectType().ordinal()); + writer.putInt(heraldryCharacter.getGuild().getNation().getObjectUUID()); + } else { + writer.putInt(0); + writer.putInt(0); + } + } else { + writer.putLong(0); + writer.putLong(0); + } + writer.putShort((short) 0); + writer.put((byte) 0); + writer.putString(heraldryCharacter.getName()); + if (heraldryCharacter.getGuild() != null) + GuildTag._serializeForDisplay(heraldryCharacter.getGuild().getGuildTag(), writer); + else { + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + } + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + if (heraldryCharacter.getGuild() == null) { + writer.putString("Errant"); + writer.putString("Errant"); + } else { + writer.putString(heraldryCharacter.getGuild().getName()); + if (heraldryCharacter.getGuild().getNation() == null) + writer.putString("Errant"); + else + writer.putString(heraldryCharacter.getGuild().getNation().getName()); + } + writer.putInt(0); + } + + } + + } + + private void readAddHealrdy(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + this.playerType = reader.getInt(); //player object type; + this.playerID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.getInt(); + } + + private void writeAddHealrdy(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(this.playerType); //player object type; + writer.putInt(this.playerID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + + public int getMessageType() { + return this.messageType; + } + + public void setMessageType(int value) { + this.messageType = value; + } + + public ArrayList getList() { + return this.characterList; + } + + public void setList(ArrayList value) { + this.characterList = value; + } + + public void updateMsg(int messageType, ArrayList list) { + this.messageType = messageType; + this.characterList = list; + this.configure(); + } + + public int getInviteType() { + return inviteType; + } + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } + + public int getGuildID() { + return guildID; + } + + public void setGuildID(int guildID) { + this.guildID = guildID; + } + + public int getNationID() { + return nationID; + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public boolean isReverseKOS() { + return reverseKOS; + } + + public void setReverseKOS(boolean reverseKOS) { + this.reverseKOS = reverseKOS; + } + + + private void showNullHeraldryCharacter(ByteBufferWriter writer) { + writer.put((byte) 1); + writer.putInt(0); + writer.putInt(0); + + writer.putInt(6); + writer.putInt(0); + writer.putInt(0); + + + writer.putLong(0); + writer.putLong(0); + writer.putShort((short) 0); + writer.put((byte) 0); + writer.putInt(0); + + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + } +} diff --git a/src/engine/net/client/msg/OpenTradeWindowMsg.java b/src/engine/net/client/msg/OpenTradeWindowMsg.java new file mode 100644 index 00000000..05af29b5 --- /dev/null +++ b/src/engine/net/client/msg/OpenTradeWindowMsg.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Open trade window + * + * @author Eighty + */ +public class OpenTradeWindowMsg extends ClientNetMsg { + + private int unknown01; + private int playerType; + private int playerID; + private int targetType; + private int targetID; + + /** + * This is the general purpose constructor + */ + public OpenTradeWindowMsg(int unknown01, AbstractGameObject player, AbstractGameObject target) { + super(Protocol.INITIATETRADEHUDS); + this.unknown01 = unknown01; + this.playerType = player.getObjectType().ordinal(); + this.playerID = player.getObjectUUID(); + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public OpenTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.INITIATETRADEHUDS, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + playerType = reader.getInt(); + playerID = reader.getInt(); + targetType = reader.getInt(); + targetID = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putInt(playerType); + writer.putInt(playerID); + writer.putInt(targetType); + writer.putInt(targetID); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + +} diff --git a/src/engine/net/client/msg/OpenVaultMsg.java b/src/engine/net/client/msg/OpenVaultMsg.java new file mode 100644 index 00000000..61aca7e9 --- /dev/null +++ b/src/engine/net/client/msg/OpenVaultMsg.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Vault window opened + * + * @author Eighty + */ +public class OpenVaultMsg extends ClientNetMsg { + + private int playerType; + private int playerID; + private int npcType; + private int npcID; + + + /** + * This is the general purpose constructor. + */ + public OpenVaultMsg(AbstractGameObject ago, AbstractGameObject target) { + super(Protocol.OPENVAULT); + this.playerType = ago.getObjectType().ordinal(); + this.playerID = ago.getObjectUUID(); + this.npcType = target.getObjectType().ordinal(); + this.npcID = target.getObjectUUID(); + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public OpenVaultMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.OPENVAULT, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + playerType = reader.getInt(); + playerID = reader.getInt(); + npcType = reader.getInt(); + npcID = reader.getInt(); + + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(playerType); + writer.putInt(playerID); + writer.putInt(npcType); + writer.putInt(npcID); + } + +} diff --git a/src/engine/net/client/msg/OrderNPCMsg.java b/src/engine/net/client/msg/OrderNPCMsg.java new file mode 100644 index 00000000..ad933cc7 --- /dev/null +++ b/src/engine/net/client/msg/OrderNPCMsg.java @@ -0,0 +1,204 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.math.Vector3fImmutable; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +import java.util.ArrayList; + +public class OrderNPCMsg extends ClientNetMsg { + + // 2 = manage this asset. 20 = manage entire city + private int objectType; + private int npcUUID; + private int buildingUUID; + private int unknown02; + private ArrayList patrolPoints; + private ArrayList sentryPoints; + private int patrolSize; + private int sentrySize; + + private int actionType; + private float buySellPercent; + + /** + * This is the general purpose constructor + */ + public OrderNPCMsg() { + super(Protocol.ORDERNPC); + this.actionType = 0; + this.unknown02 = 0; + this.npcUUID = 0; + this.buildingUUID = 0; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public OrderNPCMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ORDERNPC, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + actionType = reader.getInt(); + if (this.actionType == 28) { + this.handleCityCommand(reader); + return; + } + unknown02 = reader.getInt(); + this.objectType = reader.getInt(); // Object Type Padding + npcUUID = reader.getInt(); + reader.getInt(); // Object Type Padding + buildingUUID = reader.getInt(); + this.buySellPercent = reader.getFloat(); + if (actionType > 6 && actionType < 13) + reader.getInt(); + + + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(actionType); + writer.putInt(unknown02); + writer.putInt(GameObjectType.NPC.ordinal()); + writer.putInt(npcUUID); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(buildingUUID); + writer.putFloat(this.buySellPercent); + writer.putInt(0); + + + } + + private void handleCityCommand(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.buildingUUID = reader.getInt(); + reader.get(); + reader.get(); + reader.getInt(); + patrolSize = reader.getInt(); + if (patrolSize > 0) { + this.patrolPoints = new ArrayList<>(); + for (int i = 0; i < patrolSize; i++) { + float x = reader.getFloat(); + float y = reader.getFloat(); + float z = reader.getFloat(); + if (this.patrolPoints.size() < 4) + this.patrolPoints.add(new Vector3fImmutable(x, y, z)); + } + } + sentrySize = reader.getInt(); + if (sentrySize > 0) { + this.sentryPoints = new ArrayList<>(); + for (int i = 0; i < sentrySize; i++) { + float x = reader.getFloat(); + float y = reader.getFloat(); + float z = reader.getFloat(); + if (this.sentryPoints.size() < 4) + this.sentryPoints.add(new Vector3fImmutable(x, y, z)); + } + } + reader.getInt(); + reader.getInt(); + } + + /** + * @return the npcUUID + */ + public int getNpcUUID() { + return npcUUID; + } + + + public int getActionType() { + return actionType; + } + + + public int getBuildingUUID() { + return buildingUUID; + } + + /** + * @param buildingUUID the buildingUUID to set + */ + public void setBuildingUUID(int buildingUUID) { + this.buildingUUID = buildingUUID; + } + + public float getBuySellPercent() { + return buySellPercent; + } + + public void setBuySellPercent(float buySellPercent) { + this.buySellPercent = buySellPercent; + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int objectType) { + this.objectType = objectType; + } + + public ArrayList getPatrolPoints() { + return patrolPoints; + } + + public ArrayList getSentryPoints() { + return sentryPoints; + } + + public int getPatrolSize() { + return patrolSize; + } + + public void setPatrolSize(int patrolSize) { + this.patrolSize = patrolSize; + } + + public int getSentrySize() { + return sentrySize; + } + + public void setSentrySize(int sentrySize) { + this.sentrySize = sentrySize; + } + +} + +//Debug Info +//Run: Failed to make object TEMPLATE:135700 INSTANCE:1717987027141... (t=50.46) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027161... (t=50.46) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027177... (t=50.67) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:60040 INSTANCE:1717987027344... (t=50.87) (r=7/4/2011 11:56:39) +//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:3 INSTANCE:1717987027164... (t=50.88) (r=7/4/2011 11:56:39) + diff --git a/src/engine/net/client/msg/PassiveMessageTriggerMsg.java b/src/engine/net/client/msg/PassiveMessageTriggerMsg.java new file mode 100644 index 00000000..87070bdf --- /dev/null +++ b/src/engine/net/client/msg/PassiveMessageTriggerMsg.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class PassiveMessageTriggerMsg extends ClientNetMsg { + + private byte animation; //not sure if it's animation, 0 or 1. + + /** + * This is the general purpose constructor. + */ + public PassiveMessageTriggerMsg(byte animation) { + super(Protocol.PASSIVEMESSAGETRIGGER); + this.animation = animation; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PassiveMessageTriggerMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.PASSIVEMESSAGETRIGGER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.put(this.animation); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.animation = reader.get(); + } + + public byte getAnimation() { + return this.animation; + } + + public void setAnimation(byte value) { + this.animation = value; + } + +} diff --git a/src/engine/net/client/msg/PerformActionMsg.java b/src/engine/net/client/msg/PerformActionMsg.java new file mode 100644 index 00000000..0c838b09 --- /dev/null +++ b/src/engine/net/client/msg/PerformActionMsg.java @@ -0,0 +1,290 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class PerformActionMsg extends ClientNetMsg { + + protected int powerUsedID; + protected int numTrains; + protected int sourceType; + protected int sourceID; + protected int targetType; + protected int targetID; + + protected float targetX; + protected float targetY; + protected float targetZ; + protected int unknown04; //1, 2, 6 + protected int unknown05; + + protected int realTrains; //not serialized. Used for mob AI tracking. + + /** + * This is the general purpose constructor. + */ + public PerformActionMsg() { + super(Protocol.POWER); + } + + public PerformActionMsg(int powerUsedID, int numTrains, int sourceType, int sourceID, int targetType, int targetID, float targetX, float targetY, float targetZ, int unknown04, int unknown05) { + super(Protocol.POWER); + this.powerUsedID = powerUsedID; + this.numTrains = numTrains; + this.sourceType = sourceType; + this.sourceID = sourceID; + this.targetType = targetType; + this.targetID = targetID; + this.targetX = targetX; + this.targetY = targetY; + this.targetZ = targetZ; + this.unknown04 = unknown04; + this.unknown05 = unknown05; + + this.realTrains = this.numTrains; + } + + + public PerformActionMsg(PerformActionMsg msg) { + super(Protocol.POWER); + this.powerUsedID = msg.powerUsedID; + this.numTrains = msg.numTrains; + this.sourceType = msg.sourceType; + this.sourceID = msg.sourceID; + this.targetType = msg.targetType; + this.targetID = msg.targetID; + this.targetX = msg.targetX; + this.targetY = msg.targetY; + this.targetZ = msg.targetZ; + this.unknown04 = msg.unknown04; + this.unknown05 = msg.unknown05; + this.realTrains = msg.realTrains; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PerformActionMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.POWER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.powerUsedID); + writer.putInt(this.numTrains); + + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + + writer.putFloat(this.targetX); + writer.putFloat(this.targetY); + writer.putFloat(this.targetZ); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.powerUsedID = reader.getInt(); + this.numTrains = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + + this.targetX = reader.getFloat(); + this.targetY = reader.getFloat(); + this.targetZ = reader.getFloat(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); //2=FailToCast, 3=Miss, 4=Dodge, 5=Immune, 6=resisted, 7=targetDead, 8=PowerInterupted, 9=NoValidTargets, 10=NotBeenGrantedPower + this.realTrains = this.numTrains; + } + + /** + * @return the powerUsedID + */ + public int getPowerUsedID() { + return powerUsedID; + } + + /** + * @param powerUsedID the powerUsedID to set + */ + public void setPowerUsedID(int powerUsedID) { + this.powerUsedID = powerUsedID; + } + + /** + * @return the numTrains + */ + public int getNumTrains() { + return numTrains; + } + + /** + * @param numTrains the numTrains to set + */ + public void setNumTrains(int numTrains) { + this.numTrains = numTrains; + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + /** + * @param sourceType the sourceType to set + */ + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + /** + * @param sourceID the sourceID to set + */ + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @param targetType the targetType to set + */ + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + /** + * @param targetID the targetID to set + */ + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + /** + * @return the unknown01 + */ + public float getTargetX() { + return targetX; + } + + /** + * + */ + public void setTargetX(float targetX) { + this.targetX = targetX; + } + + /** + * @return the unknown02 + */ + public float getTargetY() { + return targetY; + } + + public void setTargetY(float targetY) { + this.targetY = targetY; + } + + /** + * @return the unknown03 + */ + public float getTargetZ() { + return targetZ; + } + + + public void setTargetZ(float targetZ) { + this.targetZ = targetZ; + } + + public Vector3fImmutable getTargetLoc() { + return new Vector3fImmutable(this.targetX, this.targetY, this.targetZ); + } + + public void setTargetLoc(Vector3f targetLoc) { + this.targetX = targetLoc.x; + this.targetY = targetLoc.y; + this.targetZ = targetLoc.z; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public int getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + public int getRealTrains() { + return this.realTrains; + } +} diff --git a/src/engine/net/client/msg/PetAttackMsg.java b/src/engine/net/client/msg/PetAttackMsg.java new file mode 100644 index 00000000..40ae985b --- /dev/null +++ b/src/engine/net/client/msg/PetAttackMsg.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class PetAttackMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + + /** + * This is the general purpose constructor. + */ + public PetAttackMsg() { + super(Protocol.ARCPETATTACK); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PetAttackMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCPETATTACK, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + } + + public int getTargetType() { + return this.targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return this.targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } +} diff --git a/src/engine/net/client/msg/PetCmdMsg.java b/src/engine/net/client/msg/PetCmdMsg.java new file mode 100644 index 00000000..64095719 --- /dev/null +++ b/src/engine/net/client/msg/PetCmdMsg.java @@ -0,0 +1,68 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class PetCmdMsg extends ClientNetMsg { + + private int type; + + //1: stop attack + //2: dismiss + //3: toggle assist + //5: rest + + /** + * This is the general purpose constructor. + */ + public PetCmdMsg(int type) { + super(Protocol.ARCPETCMD); + this.type = type; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PetCmdMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCPETCMD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(type); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + } + + public int getType() { + return this.type; + } + + public void setType(int value) { + this.type = value; + } +} diff --git a/src/engine/net/client/msg/PetMsg.java b/src/engine/net/client/msg/PetMsg.java new file mode 100644 index 00000000..ca378aae --- /dev/null +++ b/src/engine/net/client/msg/PetMsg.java @@ -0,0 +1,117 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Mob; + +public class PetMsg extends ClientNetMsg { + + private int type; //5 or 6 + private Mob pet; + + /** + * This is the general purpose constructor. + */ + public PetMsg(int type, Mob pet) { + super(Protocol.PET); + if (this.type != 6) + this.type = 5; + this.pet = pet; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PetMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.PET, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.type); + + if (this.pet != null) { + writer.putInt(pet.getObjectType().ordinal()); + writer.putInt(pet.getObjectUUID()); + } else { + writer.putInt(0); + writer.putInt(0); + } + + if (type == 6) { + writer.putInt(0); + } else if (type == 5) { + if (pet != null) { + writer.putInt((int) (pet.getCurrentHitpoints() / pet.getHealthMax())); //suspect %health left + writer.putInt((int) (pet.getMana() / pet.getManaMax())); //suspect %mana left + writer.putInt((int) (pet.getStamina() / pet.getStaminaMax())); //suspect %stamina left + writer.putString(pet.getName()); + writer.putInt(0); + writer.put((byte) 0); + } else { + writer.putInt(0); //suspect %health left + writer.putInt(0); //suspect %mana left + writer.putInt(0); //suspect %stamina left + writer.putString("No Pet"); + writer.putInt(0); + writer.put((byte) 0); + } + + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + reader.getInt(); + int petID = reader.getInt(); + this.pet = Mob.getFromCache(petID); + if (this.type == 5) { + reader.getInt(); + } else if (this.type == 6) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getString(); + reader.getInt(); + reader.get(); + } + } + + public int getType() { + return this.type; + } + + public void setType(int value) { + this.type = value; + } + + public Mob getPet() { + return this.pet; + } + + public void setPet(Mob value) { + this.pet = value; + } +} diff --git a/src/engine/net/client/msg/PetitionReceivedMsg.java b/src/engine/net/client/msg/PetitionReceivedMsg.java new file mode 100644 index 00000000..fa66f1a9 --- /dev/null +++ b/src/engine/net/client/msg/PetitionReceivedMsg.java @@ -0,0 +1,97 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +import java.util.stream.IntStream; + +import static engine.net.client.handlers.PetitionReceivedMsgHandler.PETITION_NEW; + + +public class PetitionReceivedMsg extends ClientNetMsg { + + public int petition; + public int unknown01; + public int unknown02; + public byte unknownByte01; + public int unknown03; + public int unknown04; + public int unknown05; + public int unknown06; + public int type; + public int subType; + public String compType; + public String language; + public int stringCount; + public String message; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PetitionReceivedMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CUSTOMERPETITION, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.petition); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.put(this.unknownByte01); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + petition = reader.getInt(); + + if (petition == PETITION_NEW) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknownByte01 = reader.get(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.getInt(); + this.type = reader.getInt(); + this.subType = reader.getInt(); + this.compType = reader.getString(); + this.language = reader.getString(); + this.stringCount = reader.getInt(); + this.message = ""; + IntStream.range(0, stringCount).forEach($ -> this.message += reader.getString()); + + } else { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknownByte01 = reader.get(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + } + } + +} diff --git a/src/engine/net/client/msg/PlaceAssetMsg.java b/src/engine/net/client/msg/PlaceAssetMsg.java new file mode 100644 index 00000000..c59bd23f --- /dev/null +++ b/src/engine/net/client/msg/PlaceAssetMsg.java @@ -0,0 +1,621 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.math.Vector3fImmutable; +import engine.net.*; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.Zone; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class PlaceAssetMsg extends ClientNetMsg { + + /* + Client -> Server + //Type 1 + //940962DF 00000001 00000000 0A400000 03903DC1 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00 + //00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00 + //00000000 + //00000000 + //00000000 + //Type3 + //940962DF 00000003 00000000 00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00 + //00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01 + //00000000 + //00000001 + // 00000000 0013FF24 475765FD 417BD060 C794FF75 B33BBD2E 00000000 3F800000 00000000 <-placement info + //00000000 + + + Server -> Client + //Type 2 (Asset list / walls) + //940962DF 00000002 00000000 00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 01 + //475725EC 412BD080 C794DF86 44600000 44600000 44600000 43000000 43000000 43000000 445AC000 445AC000 01 + //00000000 + //00000000 + //00000006 <-building list for walls + // 00000000 0013FA74 00061A80 <-wall ID and cost + // 00000000 0013FBA0 000249F0 + // 00000000 0013FCCC 000186A0 + // 00000000 0013FDF8 0007A120 + // 00000000 0013FF24 0007A120 + // 00000000 004D5FD0 0007A120 + //Type 2 (Single asset) + //940962DF 00000002 00000000 00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 01 + //475725EC 412BD080 C794DF86 44600000 44600000 44600000 43000000 43000000 43000000 445AC000 445AC000 00 + //00000001 + // 00000000 00063060 00000001 <-blueprintUUID, 1 Building + //00000000 + //00000000 + //Type 0 (Response Error) + //940962DF 00000000 00000006 0000001b + //430061006e006e006f007400200070006c00610063006500200061007300730065007400200069006e00200077006100740065007200 + //00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00 + //00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00 + //00000000 + //00000000 + //00000000 + //Type 4 (Response Success (place asset)) + //940962DF 00000000 00000002 0000000D 00460065007500640061006C0020004300680075007200630068 + //00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00 + //00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00 + //00000000 + //00000000 + //00000000 + */ + + //UseItem(C->S)->Type 2(S->C)->Type 1(C->S) <-close placement window, no response + //UseItem(C->S)->Type 2(S->C)->Type 3(C->S)->Type 0(S->C) <-Attempt place asset, with error response + //UseItem(C->S)->Type 2(S->C)->Type 3(C->S)->Type 4(S->C) <-Attempt place asset, with success response + + /* Error msg codes + //1 "msg append here" <- what's in msg string. + //2 Conflict with "msg append here" + //3 Conflict between proposed assets + //4 Asset "msg append here" Cannot be isolated. + //5 Asset "msg append here" is outside the fortress zone. + //6 Cannot place the asset in water. + //7 Cannot place asset on land. + //8 NULL template for asset + //9 You must be a guild member to place this asset + //10 You must be a guild leader to place this asset + //11 No city asset template + //12 Only leaders of your guild can place fortresses + //13 Your guild cannot place fortress placements here + //14 This city architechture cannot be placed in this zone + //15 Cannot place assets in peace zone + //16 You do not belong to a guild + //17 Yours is not an errant or swarn guild + //18 There are no guild trees to be found + //19 NULL tree city asset + //20 You cannot place a bane circle on a tree your affiliated with + //21 This tree cannot be affected by bane circles + //22 Bane circles cannot effect dead trees + //23 A bane circle is already attached to nearest tree + //24 Banecircle is too far from guild + //25 Banecircle is too close to guild tree + //26 Cannot find deed in inventory + //27 Target object is not a deed + //28 You do not have enough gold to complete this request + //29 You apparently do not have access to some of these assets + //30 Insufficient deeds + //31 Unable to locate deed to this asset + //32 Unknown error occurred: 32 + //33 Unknown error occurred: 33 + //34 Unknown error occurred: 34 + //35 Unknown error occurred: 35 + //36 Unknown error occurred: 36 + //37 Unknown error occurred: 37 + //38 Unknown error occurred: 38 + //39 Too close to another tree + //40 Cannot place into occupied guild zone + //41 Cannot place outisde a guild zone + //42 Tree cannot support anymore shrines + //43 The city already has a shrine of that type + //44 You must be in a player guild to place a bane circle + //45 Tree cannot support anymore spires + //46 A spire of that type already exists + //47 Tree cannot support anymore barracks + //48 Assets (except walls) must be placed one at a time + //49 The city cannot support a warehouse at its current rank + //50 You can only have one warehouse + //51 This asset cannot be placed on city grid + //52 No city to associate asset with + //53 Buildings of war cannot be placed around a city grid unless there is an active bane + //54 You must belong to the nation of the Bane Circle or the Tree to place buildings of war. + //55 You must belong to a nation to place a bane circle + //56 The building of war must be placed closer to the city + //57 No building may be placed within this territory + //58 This territory is full, it can support no more then "msg append here" trees. + //59 No city to siege at this location. + //60 This scroll's rank is too low to bane this city + //61 The bane circle cannot support any more buildings of war + //62 The tree cannot support any more buildings of war + //63 Failure in guild tree claiming phase + //64 Your nation is already at war and your limit has been reached + //65 Unable to find a tree to target + //66 There is no bane circle to support this building of war + //67 There is no tree to support this building of war + //68 There is not tree or bane circle to support this building of war + //69 Trees must be placed within a territory + //70 Unknown error occurred: 38 + //71 This building of war may not be placed on a city grid by attackers + //72 You cannot place a bane circle while you are in a non player nation + //73 Only the guild leader or inner council may place a bane circle + //74 Only buildings of war may be placed during a bane + //75 This current vigor of the tree withstands your attempt to place a bane circle. Minutes remaining: "msg appended here" + //76 This tree cannot support towers or gatehouses + //77 This tree cannot support more towers or gatehouses + //78 This tree cannot support walls. + //79 This tree cannot support more walls. + */ + + private static final Map wallToCost; + private static final Map wallToUseId; + private static final Map> useIdToWallCostMaps; + private static final int NONE = 0; + private static final int CLIENTREQ_UNKNOWN = 1; + private static final int SERVER_OPENWINDOW = 2; + private static final int CLIENTREQ_NEWBUILDING = 3; // Request to place asset + private static final int SERVER_CLOSEWINDOW = 4; + + static { + Map map = new HashMap<>(); + map.put(454700, 100000); //Straight Outer Wall + map.put(1309900, 100000); //Irekei Outer Straight Wall + map.put(1348900, 100000); //Invorri Outer Straight Wall + map.put(454650, 150000); //Outer Wall with Stairs + map.put(455000, 150000); //Outer Wall with Tower + map.put(454550, 150000); //Outer Wall Gate + map.put(455700, 150000); //Small Gate House + map.put(1309600, 150000); //Irekei Outer Wall with Stairs + map.put(1309300, 150000); //Irekei Outer Wall Gate + map.put(1331200, 150000); //Elven Straight Outer Wall + map.put(1330900, 150000); //Elven Outer Wall with Stairs + map.put(1332100, 150000); //Elven Outer Wall with Tower + map.put(1330300, 150000); //Elven Outer Wall Gate + map.put(1348600, 150000); //Invorri Outer Wall with Stairs + map.put(1348300, 150000); //Invorri Outer Wall Gate + map.put(454750, 300000); //Concave Tower + map.put(458100, 300000); //Artillery Tower + map.put(455300, 300000); //Tower Junction + map.put(454800, 300000); //Convex Tower (inside corner) + map.put(1310200, 300000); //Irekei Concave Tower + map.put(5070800, 300000); //Irekei Artillery Tower + map.put(1310500, 300000); //Irekei Convex Tower + map.put(1330600, 300000); //Elven Gate House + map.put(1331500, 300000); //Elven Concave Tower + map.put(5070200, 300000); //Elven Artillery Tower + map.put(1332400, 300000); //Elven Tower Junction + map.put(1331800, 300000); //Elven Convex Tower + map.put(1349200, 300000); //Invorri Concave Tower + map.put(5071400, 300000); //Invorri Artillery Tower + map.put(1349500, 300000); //Invorri Convex Tower + wallToCost = Collections.unmodifiableMap(map); + } + + static { + Map map = new HashMap<>(); + ///Feudal Outer Walls + map.put(454700, 1); //Straight Outer Wall + map.put(454650, 1); //Outer Wall with Stairs + map.put(455000, 1); //Outer Wall with Tower + map.put(454550, 1); //Outer Wall Gate + map.put(455700, 1); //Small Gate House + map.put(454750, 1); //Concave Tower + map.put(458100, 1); //Artillery Tower + map.put(455300, 1); //Tower Junction + map.put(454800, 1); //Convex Tower (inside corner) + //map.put(1, 454000, 1); //Gate House (giant gatehouse) NOT USE IN GAME + //Feudal Inner Walls + /* + map.put(454100, 2); //Inner Archway + map.put(454200, 2); //Inner Wall Corner + map.put(454250, 2); //Inner Wall Gate + map.put(454300, 2); //Inner Straight Wall + map.put(454350, 2); //Inner Wall T-Junction + map.put(454400, 2); //Inner Wall Cross Junction + map.put(454850, 2); //Tower-Inner Wall Junction (T-East) Stuck inside left + map.put(454900, 2); //Tower-Inner Wall Junction (T-South) stuck inside right + map.put(454950, 2); //Tower-Inner Wall Junction (4-way) stuck inside + */ + //Irekei Outer Walls + map.put(1309900, 3); //Irekei Outer Straight Wall + map.put(1309600, 3); //Irekei Outer Wall with Stairs + map.put(1309300, 3); //Irekei Outer Wall Gate + map.put(1310200, 3); //Irekei Concave Tower + map.put(5070800, 3); //Irekei Artillery Tower + map.put(1310500, 3); //Irekei Convex Tower + //Elven Outer Walls + map.put(1331200, 4); //Elven Straight Outer Wall + map.put(1330900, 4); //Elven Outer Wall with Stairs + map.put(1332100, 4); //Elven Outer Wall with Tower + map.put(1330300, 4); //Elven Outer Wall Gate + map.put(1330600, 4); //Elven Gate House + map.put(1331500, 4); //Elven Concave Tower + map.put(5070200, 4); //Elven Artillery Tower + map.put(1332400, 4); //Elven Tower Junction + map.put(1331800, 4); //Elven Convex Tower + //Invorri Outer Walls + map.put(1348900, 5); //Invorri Outer Straight Wall + map.put(1348600, 5); //Invorri Outer Wall with Stairs + map.put(1348300, 5); //Invorri Outer Wall Gate + map.put(1349200, 5); //Invorri Concave Tower + map.put(5071400, 5); //Invorri Artillery Tower + map.put(1349500, 5); //Invorri Convex Tower + wallToUseId = Collections.unmodifiableMap(map); + } + + static { + //autoloaded based on wallToUseId and wallToCost + Map> map = new HashMap<>(); + for (Map.Entry entry : wallToUseId.entrySet()) { + int wallId = entry.getKey(); + int useId = entry.getValue(); + int cost = 0; + Integer costCheck = wallToCost.get(wallId); + if (costCheck != null) { + cost = costCheck; + } else { + throw new Error("PlaceAssetMsg: WallId '" + wallId + "' has no cost in 'wallToCost' but exists in 'useIdToWall'."); + } + if (!map.containsKey(useId)) { + map.put(useId, new HashMap<>()); + } + map.get(useId).put(wallId, cost); + } + for (Map.Entry> entry : map.entrySet()) { + map.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue())); + } + useIdToWallCostMaps = Collections.unmodifiableMap(map); + } + + private int actionType; //1,3 (recv), 0,2 (send) + private int msgID; //used on type 0, 0 on all other types + private String msg; //used on type 0 + private int contractType; //used on type 1 + private int contractID; //used on type 1 + private byte unknown01; //0x01 on type 2 (send city data). 0x00 otherwise + private float x; + private float y; + private float z; + private byte unknown02; //0x01 if data follow, 0x00 otherwise. Best guess + private ArrayList placementInfo; + + /** + * This is the general purpose constructor. + */ + public PlaceAssetMsg() { + super(Protocol.PLACEASSET); + this.placementInfo = new ArrayList<>(); + this.actionType = SERVER_OPENWINDOW; + this.msgID = 0; + this.msg = ""; + this.contractType = 0; + this.contractID = 0; + this.unknown01 = (byte) 0x00; + this.x = 0f; + this.y = 0f; + this.z = 0f; + this.unknown02 = (byte) 0x01; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PlaceAssetMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.PLACEASSET, origin, reader); + } + + private static Map getBuildingList(int buildingID) { + if (useIdToWallCostMaps.containsKey(buildingID)) { + return useIdToWallCostMaps.get(buildingID); + } + return new HashMap<>(0); + } + + public static int getWallCost(int blueprintUUID) { + if (wallToCost.containsKey(blueprintUUID)) { + return wallToCost.get(blueprintUUID); + } + Logger.warn("Cost of Wall '" + blueprintUUID + "' was requested but no cost is configured for that wallId."); + return 0; + } + + public static void sendPlaceAssetError(ClientConnection origin, int errorID, String stringData) { + + PlaceAssetMsg outMsg; + + outMsg = new PlaceAssetMsg(); + outMsg.actionType = 0; + outMsg.msgID = errorID; + outMsg.msg = stringData; + + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + public static void sendPlaceAssetConfirmWall(ClientConnection origin, Zone zone) { + + PlaceAssetMsg outMsg = new PlaceAssetMsg(); + outMsg.actionType = 0; + outMsg.msgID = 0; + outMsg.msg = ""; + outMsg.x = zone.getLoc().x + 64; + outMsg.y = zone.getLoc().y; + outMsg.z = zone.getLoc().z + 64; + + + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + writer.putInt(this.actionType); + + if (this.actionType == NONE) { + writer.putInt(this.msgID); + if (this.msgID != 0) + writer.putString(this.msg); + } else if (this.actionType == SERVER_CLOSEWINDOW) { + //writer.putInt(1); //Qty of assets placed?? A 0 will crash the client. Any value >0 seems to do the same thing. + writer.putInt(0); + } else { + writer.putInt(0); + } + + writer.putInt(this.contractType); + writer.putInt(this.contractID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putFloat(1f); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + if (this.actionType == SERVER_OPENWINDOW || (this.actionType == NONE && this.msgID == 0)) { + //send Place Asset Msg + writer.put((byte) 0x01); + writer.putFloat(x); + writer.putFloat(y); + writer.putFloat(z); + for (int i = 0; i < 3; i++) + writer.putFloat(896); // city Bounds full extent. + for (int i = 0; i < 3; i++) + writer.putFloat(128); //grid dimensions + PlacementInfo pi = (this.placementInfo.size() > 0) ? this.placementInfo.get(0) : null; + int buildingID = (pi != null) ? pi.getBlueprintUUID() : 0; + if (buildingID == 24200) { + writer.putFloat(875); + writer.putFloat(875); + } else { + writer.putFloat(576); + writer.putFloat(576); + } + + + if (buildingID < 6) { + //place wall lists + writer.put((byte) 0x01); + writer.putInt(0); + writer.putInt(0); + Map buildings = getBuildingList(buildingID); + writer.putInt(buildings.size()); + for (int bID : buildings.keySet()) { + writer.putInt(0); + writer.putInt(bID); + writer.putInt(buildings.get(bID)); + } + } else { + //send individual building + writer.put((byte) 0x00); + writer.putInt(1); + writer.putInt(0); + writer.putInt(buildingID); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + } + } else { + //Send Server response to client placing asset + writer.put((byte) 0x00); + for (int i = 0; i < 11; i++) + writer.putFloat(0f); + writer.put((byte) 0x00); + writer.putInt(0); + if (this.placementInfo == null) + writer.putInt(0); + else { + writer.putInt(this.placementInfo.size()); + for (PlacementInfo placementInfo : this.placementInfo) { + writer.putInt(0); + writer.putInt(placementInfo.blueprintUUID); + writer.putVector3f(placementInfo.loc); + writer.putFloat(placementInfo.w); + writer.putVector3f(placementInfo.rot); + } + } + + writer.putInt(0); + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.placementInfo = new ArrayList<>(); + this.actionType = reader.getInt(); + reader.getInt(); + this.contractType = reader.getInt(); + this.contractID = reader.getInt(); + for (int i = 0; i < 7; i++) + reader.getInt(); + reader.get(); + for (int i = 0; i < 11; i++) + reader.getInt(); + reader.get(); + reader.getInt(); + int placementInfo = reader.getInt(); + for (int i = 0; i < placementInfo; i++) { + reader.getInt(); + PlacementInfo pi = new PlacementInfo(reader.getInt(), reader.getFloat(), reader.getFloat(), + reader.getFloat(), reader.getFloat(), reader.getFloat(), reader.getFloat(), reader.getFloat()); + this.placementInfo.add(pi); + } + reader.getInt(); + } + + public int getActionType() { + return this.actionType; + } + + public void setActionType(int value) { + this.actionType = value; + } + + public int getID() { + return this.msgID; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String value) { + this.msg = value; + } + + public int getContractType() { + return this.contractType; + } + + public void setContractType(int value) { + this.contractType = value; + } + + public int getContractID() { + return this.contractID; + } + + public void setContractID(int value) { + this.contractID = value; + } + + public byte getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(byte value) { + this.unknown01 = value; + } + + public float getX() { + return this.x; + } + + public void setX(float value) { + this.x = value; + } + + public float getY() { + return this.y; + } + + public void setY(float value) { + this.y = value; + } + + public float getZ() { + return this.z; + } + + public void setZ(float value) { + this.z = value; + } + + public byte getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(byte value) { + this.unknown02 = value; + } + + public ArrayList getPlacementInfo() { + return this.placementInfo; + } + + public PlacementInfo getFirstPlacementInfo() { + if (this.placementInfo.size() > 0) + return this.placementInfo.get(0); + return null; + } + + public void setMsgID(int value) { + this.msgID = value; + } + + public void addPlacementInfo(int ID) { + PlacementInfo pi = new PlacementInfo(ID, 0f, 0f, 0f, 0f, 0f, 0f, 0f); + this.placementInfo.add(pi); + } + + public static class PlacementInfo { + int blueprintUUID; + Vector3fImmutable loc; + float w; + Vector3fImmutable rot; + + public PlacementInfo(int blueprintUUID, float locX, float locY, float locZ, float w, float rotX, float rotY, float rotZ) { + this.blueprintUUID = blueprintUUID; + this.loc = new Vector3fImmutable(locX, locY, locZ); + this.w = w; + this.rot = new Vector3fImmutable(rotX, rotY, rotZ); + } + + public int getBlueprintUUID() { + return this.blueprintUUID; + } + + public Vector3fImmutable getLoc() { + return this.loc; + } + + public void setLoc(Vector3fImmutable loc) { + this.loc = loc; + } + + public float getW() { + return this.w; + } + + public Vector3fImmutable getRot() { + return this.rot; + } + } +} diff --git a/src/engine/net/client/msg/PowerProjectileMsg.java b/src/engine/net/client/msg/PowerProjectileMsg.java new file mode 100644 index 00000000..0a7f46c2 --- /dev/null +++ b/src/engine/net/client/msg/PowerProjectileMsg.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + + +public class PowerProjectileMsg extends ClientNetMsg { + private AbstractWorldObject source; + private AbstractWorldObject target; + private float range = 10; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + + public PowerProjectileMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCPOWERPROJECTILE, origin, reader); + } + + public PowerProjectileMsg() { + super(Protocol.ARCPOWERPROJECTILE); + } + + public PowerProjectileMsg(AbstractWorldObject source, AbstractWorldObject target) { + super(Protocol.ARCPOWERPROJECTILE); + this.source = source; + this.target = target; + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + // Pre-cache and configure values so they are available when we serialize + + public void configure() { + + if (this.source == null) + return; + + if (this.target == null) + return; + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + engine.math.Vector3fImmutable faceDir = this.source.getLoc().subtract2D(target.getLoc()).normalize(); + engine.math.Vector3fImmutable newLoc = faceDir.scaleAdd(range, target.getLoc()); + + newLoc = newLoc.setY(newLoc.getY() + range); + + writer.putInt(this.source.getObjectType().ordinal()); + writer.putInt(this.source.getObjectUUID()); + writer.putVector3f(newLoc); + + } + + public float getRange() { + return range; + } + + public void setRange(float range) { + this.range = range; + } + +} diff --git a/src/engine/net/client/msg/PromptRecallMsg.java b/src/engine/net/client/msg/PromptRecallMsg.java new file mode 100644 index 00000000..d550bed4 --- /dev/null +++ b/src/engine/net/client/msg/PromptRecallMsg.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class PromptRecallMsg extends ClientNetMsg { + + private byte confirmation; + + /** + * This is the general purpose constructor. + */ + public PromptRecallMsg() { + super(Protocol.ARCPROMPTRECALL); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public PromptRecallMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCPROMPTRECALL, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.put((byte) 0x01); // tried 0 and 1 + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.confirmation = reader.get(); + } + + public boolean getConfirmed() { + + return confirmation == 1; + } +} \ No newline at end of file diff --git a/src/engine/net/client/msg/RandomMsg.java b/src/engine/net/client/msg/RandomMsg.java new file mode 100644 index 00000000..e2b12421 --- /dev/null +++ b/src/engine/net/client/msg/RandomMsg.java @@ -0,0 +1,93 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class RandomMsg extends ClientNetMsg { + + private int max; + private int roll; + private int sourceType; + private int sourceID; + + /** + * This is the general purpose constructor. + */ + + public RandomMsg() { + super(Protocol.RANDOM); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public RandomMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.RANDOM, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.max); + writer.putInt(this.roll); + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.max = reader.getInt(); + this.roll = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + } + + public int getMax() { + return this.max; + } + + public void setMax(int value) { + this.max = value; + } + + public int getRoll() { + return this.roll; + } + + public void setRoll(int value) { + this.roll = value; + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } +} diff --git a/src/engine/net/client/msg/RecommendNationMsg.java b/src/engine/net/client/msg/RecommendNationMsg.java new file mode 100644 index 00000000..f24ea4c1 --- /dev/null +++ b/src/engine/net/client/msg/RecommendNationMsg.java @@ -0,0 +1,89 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class RecommendNationMsg extends ClientNetMsg { + + + private int guildID; + private byte ally; + private byte enemy; + + + public RecommendNationMsg(PlayerCharacter player) { + super(Protocol.RECOMMENDNATION); + + } + + public RecommendNationMsg() { + super(Protocol.RECOMMENDNATION); + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RecommendNationMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.RECOMMENDNATION, origin, reader); + } + //CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD. + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + this.guildID = reader.getInt(); + this.ally = reader.get(); + this.enemy = reader.get(); + } + + + // Precache and configure this message before we serialize it + + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(guildID); + writer.put(this.ally); + writer.put(this.enemy); + } + + public int getGuildID() { + return guildID; + } + + public byte getAlly() { + return ally; + } + + public byte getEnemy() { + return enemy; + } + +} diff --git a/src/engine/net/client/msg/RecvSummonsRequestMsg.java b/src/engine/net/client/msg/RecvSummonsRequestMsg.java new file mode 100644 index 00000000..2dd6d5d2 --- /dev/null +++ b/src/engine/net/client/msg/RecvSummonsRequestMsg.java @@ -0,0 +1,111 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class RecvSummonsRequestMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private String sourceName; + private String locationName; //where being summoned to + private boolean accepted; + + /** + * This is the general purpose constructor. + */ + public RecvSummonsRequestMsg(int sourceType, int sourceID, String sourceName, String locationName, boolean accepted) { + super(Protocol.ARCSUMMON); + this.sourceType = sourceType; + this.sourceID = sourceID; + this.sourceName = sourceName; + this.locationName = locationName; + this.accepted = accepted; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RecvSummonsRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCSUMMON, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putString(this.sourceName); + writer.putString(this.locationName); + writer.put(this.accepted ? (byte) 1 : (byte) 0); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.sourceName = reader.getString(); + this.locationName = reader.getString(); + this.accepted = (reader.get() == 1) ? true : false; + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public String getSourceName() { + return this.sourceName; + } + + public void setSourceName(String value) { + this.sourceName = value; + } + + public String getLocationName() { + return this.locationName; + } + + public void setLocationName(String value) { + this.locationName = value; + } + + public boolean accepted() { + return this.accepted; + } + + public void setAccepted(boolean value) { + this.accepted = value; + } +} diff --git a/src/engine/net/client/msg/RecyclePowerMsg.java b/src/engine/net/client/msg/RecyclePowerMsg.java new file mode 100644 index 00000000..015e5164 --- /dev/null +++ b/src/engine/net/client/msg/RecyclePowerMsg.java @@ -0,0 +1,62 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class RecyclePowerMsg extends ClientNetMsg { + + protected int token; + + public RecyclePowerMsg(int token) { + super(Protocol.RECYCLEPOWER); + this.token = token; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RecyclePowerMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.RECYCLEPOWER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.token); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.token = reader.getInt(); + } + + public int getToken() { + return this.token; + } + + public void setToken(int value) { + this.token = value; + } + +} diff --git a/src/engine/net/client/msg/RefineMsg.java b/src/engine/net/client/msg/RefineMsg.java new file mode 100644 index 00000000..81968961 --- /dev/null +++ b/src/engine/net/client/msg/RefineMsg.java @@ -0,0 +1,240 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.gameManager.SessionManager; +import engine.net.*; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.*; +import engine.server.MBServerStatics; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class RefineMsg extends ClientNetMsg { + + private int npcType; + private int npcID; + private int unknown01; + private int type; + private int token; + private int unknown02; + + /** + * This is the general purpose constructor. + */ + public RefineMsg() { + super(Protocol.ARCUNTRAINABILITY); + } + + public RefineMsg(int npcType, int npcID, int type, int token) { + super(Protocol.ARCUNTRAINABILITY); + this.npcType = npcType; + this.npcID = npcID; + this.unknown01 = 1; + this.type = type; + this.token = token; + this.unknown02 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RefineMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCUNTRAINABILITY, origin, reader); + } + + public static void refine(RefineMsg msg, ClientConnection origin) { + if (origin == null) + return; + PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); + if (pc == null) + return; + NPC npc = NPC.getFromCache(msg.npcID); + if (npc == null) + return; + int type = msg.type; + int token = msg.token; + boolean worked = false; + boolean skillPower = true; + if (type == 0) { //refine skill + worked = refineSkill(origin, pc, token, msg); + } else if (type == 1) { //refine power + worked = refinePower(origin, pc, token, msg); + } else if (type == 2) { //refine stat + worked = refineStat(origin, pc, token, msg); + skillPower = false; + } + + if (worked) { + + //update player + pc.applyBonuses(); + pc.getCharItemManager().RemoveEquipmentFromLackOfSkill(pc, true); + + //echo refine message back + + Dispatch dispatch = Dispatch.borrow(pc, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + // if (type == 0 && token == 1488335491){ + // dispatch = Dispatch.borrow(pc, msg); + // DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + // } + + //resend refine screen + + RefinerScreenMsg refinerScreenMsg = new RefinerScreenMsg(skillPower, npc.getSellPercent(pc)); //TODO set npc cost + dispatch = Dispatch.borrow(pc, refinerScreenMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + pc.recalculate(); + } + } + + private static boolean refineSkill(ClientConnection origin, PlayerCharacter pc, int token, RefineMsg msg) { + CharacterSkill skill = null; + ConcurrentHashMap skills = pc.getSkills(); + for (CharacterSkill sk : skills.values()) { + if (sk == null) + continue; + SkillsBase sb = sk.getSkillsBase(); + if (sb == null) + continue; + if (sb.getToken() == token) + skill = sk; + } + //check if player has skill to refine + if (skill == null) + return false; + //check there's a train to refine + if (skill.getNumTrains() < 1) + return false; + + //TODO verify if any skills have this as prereq + + //TODO verify if any powers have this as a prereq + //get all players powers + for (CharacterPower power : pc.getPowers().values()) { + ArrayList reqs = PowerReq.getPowerReqsForRune(power.getPowerID()); + for (PowerReq req : reqs) { + ConcurrentHashMap playerSkills = pc.getSkills(); + CharacterSkill playerSkill = playerSkills.get(token); + int currentSkillLevel = playerSkill.getTotalSkillPercet(); + if (token == req.getToken() && req.getLevel() == currentSkillLevel) { + return false; + } + } + } + + //refine skill + return skill.refine(pc); + } + + private static boolean refinePower(ClientConnection origin, PlayerCharacter pc, int token, RefineMsg msg) { + CharacterPower power = null; + ConcurrentHashMap powers = pc.getPowers(); + if (!powers.containsKey(token)) + return false; + power = powers.get(token); + if (power == null) + return false; + if (power.getTrains() < 1) + return false; + + //TODO verify if any powers have this as a prereq + + return power.refine(pc); + } + + private static boolean refineStat(ClientConnection origin, PlayerCharacter pc, int token, RefineMsg msg) { + if (token == MBServerStatics.STAT_STR_ID) + return pc.refineStr(); + if (token == MBServerStatics.STAT_DEX_ID) + return pc.refineDex(); + if (token == MBServerStatics.STAT_CON_ID) + return pc.refineCon(); + if (token == MBServerStatics.STAT_INT_ID) + return pc.refineInt(msg); + if (token == MBServerStatics.STAT_SPI_ID) + return pc.refineSpi(); + return false; + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.npcType); + writer.putInt(this.npcID); + writer.putInt(this.unknown01); + writer.putInt(this.type); + writer.putInt(this.token); + writer.putInt(this.unknown02); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.type = reader.getInt(); + this.token = reader.getInt(); + this.unknown02 = reader.getInt(); + } + + public int getNpcType() { + return this.npcType; + } + + public int getNpcID() { + return this.npcID; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getType() { + return this.type; + } + + public void setType(int value) { + this.type = value; + } + + public int getToken() { + return this.token; + } + + public void setToken(int value) { + this.token = value; + } + + public int getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } +} diff --git a/src/engine/net/client/msg/RefinerScreenMsg.java b/src/engine/net/client/msg/RefinerScreenMsg.java new file mode 100644 index 00000000..54f5e8e3 --- /dev/null +++ b/src/engine/net/client/msg/RefinerScreenMsg.java @@ -0,0 +1,117 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class RefinerScreenMsg extends ClientNetMsg { + + private int npcType; + private int npcID; + private int unknown01; //might be - 0: skills/powers, 2: stats + private float unknown02; //cost to refine + private int unknown03; + private int unknown04; + + /** + * This is the general purpose constructor. + */ + public RefinerScreenMsg(boolean skillPower, float cost) { + super(Protocol.ARCUNTRAINLIST); + if (skillPower) + this.unknown01 = 0; //skill/power screen + else + this.unknown01 = 2; //stat screen + this.unknown02 = cost; + this.unknown03 = 0; + this.unknown04 = 0; + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RefinerScreenMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCUNTRAINLIST, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.npcType); + writer.putInt(this.npcID); + writer.putInt(this.unknown01); + writer.putFloat(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + } + + public int getNpcType() { + return this.npcType; + } + + public int getNpcID() { + return this.npcID; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public float getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getUnknown03() { + return this.unknown03; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public int getUnknown04() { + return this.unknown04; + } + + public void setUnknown04(int value) { + this.unknown04 = value; + } +} diff --git a/src/engine/net/client/msg/RejectTradeRequestMsg.java b/src/engine/net/client/msg/RejectTradeRequestMsg.java new file mode 100644 index 00000000..1305a3a6 --- /dev/null +++ b/src/engine/net/client/msg/RejectTradeRequestMsg.java @@ -0,0 +1,111 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +/** + * Reject trade request + * + * @author Eighty + */ +public class RejectTradeRequestMsg extends ClientNetMsg { + + private int unknown01; //pad? + private long playerCompID; + private long targetCompID; + + /** + * This is the general purpose constructor + */ + public RejectTradeRequestMsg(int unknown01, long playerCompID, long targetCompID) { + super(Protocol.REQUESTTRADECANCEL); + this.unknown01 = unknown01; + this.playerCompID = playerCompID; + this.targetCompID = targetCompID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public RejectTradeRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTTRADECANCEL, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + playerCompID = reader.getLong(); + targetCompID = reader.getLong(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putLong(playerCompID); + writer.putLong(targetCompID); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the playerCompID + */ + public long getPlayerCompID() { + return playerCompID; + } + + /** + * @param playerCompID the playerCompID to set + */ + public void setPlayerCompID(long playerCompID) { + this.playerCompID = playerCompID; + } + + /** + * @return the targetCompID + */ + public long getTargetCompID() { + return targetCompID; + } + + /** + * @param targetCompID the targetCompID to set + */ + public void setTargetCompID(long targetCompID) { + this.targetCompID = targetCompID; + } + +} diff --git a/src/engine/net/client/msg/RemoveFriendMessage.java b/src/engine/net/client/msg/RemoveFriendMessage.java new file mode 100644 index 00000000..47c30e02 --- /dev/null +++ b/src/engine/net/client/msg/RemoveFriendMessage.java @@ -0,0 +1,64 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class RemoveFriendMessage extends ClientNetMsg { + + public int friendID; + + /** + * This is the general purpose constructor. + */ + public RemoveFriendMessage(int friendID) { + super(Protocol.REMOVEFRIEND); + this.friendID = friendID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RemoveFriendMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REMOVEFRIEND, origin, reader); + } + + /** + * Copy constructor + */ + public RemoveFriendMessage(RemoveFriendMessage msg) { + super(Protocol.REMOVEFRIEND); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //Do we even want to try this? + reader.getInt(); + this.friendID = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.friendID); + } +} diff --git a/src/engine/net/client/msg/RepairBuildingMsg.java b/src/engine/net/client/msg/RepairBuildingMsg.java new file mode 100644 index 00000000..0cb99a56 --- /dev/null +++ b/src/engine/net/client/msg/RepairBuildingMsg.java @@ -0,0 +1,104 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class RepairBuildingMsg extends ClientNetMsg { + + private int type; + private int buildingID; + private int maxHP; + private int missingHealth; + private int strongBox; + private int repairCost; + + + public RepairBuildingMsg(int buildingID, int maxHP, int missingHealth, int repairCost, int strongBox) { + super(Protocol.REPAIRBUILDING); + this.buildingID = buildingID; + this.maxHP = maxHP; + this.missingHealth = missingHealth; + this.repairCost = repairCost; + this.strongBox = strongBox; + } + + public RepairBuildingMsg() { + super(Protocol.REPAIRBUILDING); + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RepairBuildingMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REPAIRBUILDING, origin, reader); + } + //CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD. + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + reader.getInt(); //Building Type + this.buildingID = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + } + + + // Precache and configure this message before we serialize it + + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(0); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.putInt(this.maxHP); + writer.putInt(this.strongBox); + writer.putInt(0); //? + writer.putInt(this.repairCost); + writer.putInt(this.missingHealth); + + } + + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public int getType() { + return type; + } +} diff --git a/src/engine/net/client/msg/RepairMsg.java b/src/engine/net/client/msg/RepairMsg.java new file mode 100644 index 00000000..cd3712b5 --- /dev/null +++ b/src/engine/net/client/msg/RepairMsg.java @@ -0,0 +1,216 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Contract; +import engine.objects.NPC; + +import java.util.ArrayList; + +/** + * Sell to NPC window msg + * + * @author + */ +public class RepairMsg extends ClientNetMsg { + +//Item Types: +//1: weapon +//2: armor/cloth/shield +//5: scrolls +//8: potions +//10: charters +//13: Jewelry + + private int msgType; + //static 0 + private int unknown01; //0 or 10 + private int npcType; + private int npcID; + private NPC npc = null; + private int itemType; + private int itemID; + private int amountRepaired; + //static 0 + //static 0 //end repair req/ack here + //01 inc, 00 out + //item list + //skill list + //unk list + //static 0 //out + //static 0 //out + //static 10000 out? + //static 0 + + /** + * This is the general purpose constructor + */ + public RepairMsg() { + super(Protocol.REPAIROBJECT); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public RepairMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REPAIROBJECT, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + this.msgType = reader.getInt(); + reader.getInt(); + this.unknown01 = reader.getInt(); + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.itemType = reader.getInt(); + this.itemID = reader.getInt(); + this.amountRepaired = reader.getInt(); + reader.getInt(); + reader.getInt(); + + if (this.msgType == 1) { + reader.get(); //0x00 inc + int size = reader.getInt(); + for (int i = 0; i < size; i++) + reader.getInt(); + size = reader.getInt(); + for (int i = 0; i < size; i++) + reader.getInt(); + size = reader.getInt(); + for (int i = 0; i < size; i++) + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + } + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(this.msgType); + writer.putInt(0); + writer.putInt(this.unknown01); + writer.putInt(this.npcType); + writer.putInt(this.npcID); + writer.putInt(this.itemType); + writer.putInt(this.itemID); + writer.putInt(this.amountRepaired); + writer.putInt(0); + writer.putInt(0); + if (this.msgType == 1) { + writer.put((byte) 0x00); + Contract c = (npc != null) ? npc.getContract() : null; + if (c != null) { + ArrayList list = c.getBuyItemType(); + writer.putInt(list.size()); + for (int l : list) + writer.putInt(l); + list = c.getBuySkillToken(); + writer.putInt(list.size()); + for (int l : list) + writer.putInt(l); + list = c.getBuyUnknownToken(); + writer.putInt(list.size()); + for (int l : list) + writer.putInt(l); + } else { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + writer.putInt(0); + writer.putInt(0); + writer.putInt(10000); + writer.putInt(0); + } + } + + public int getMsgType() { + return this.msgType; + } + + public void setMsgType(int value) { + this.msgType = value; + } + + public int getNPCType() { + return this.npcType; + } + + public void setNPCType(int value) { + this.npcType = value; + } + + public int getNPCID() { + return this.npcID; + } + + public void setNPCID(int value) { + this.npcID = value; + } + + public int getItemType() { + return this.itemType; + } + + public void setItemType(int value) { + this.itemType = value; + } + + public int getItemID() { + return this.itemID; + } + + public void setItemID(int value) { + this.itemID = value; + } + + public int getAmountRepaired() { + return this.amountRepaired; + } + + public NPC getNPC() { + return this.npc; + } + + public void setNPC(NPC value) { + this.npc = value; + } + + public void setAmountRrepaired(int value) { + this.amountRepaired = value; + } + + public void setupRepairAck(int amountRepaired) { + this.unknown01 = 10; + this.amountRepaired = amountRepaired; + } + + public void setRepairWindowAck(NPC npc) { + this.unknown01 = 10; + this.npc = npc; + } +} diff --git a/src/engine/net/client/msg/ReqBankInventoryMsg.java b/src/engine/net/client/msg/ReqBankInventoryMsg.java new file mode 100644 index 00000000..a2941cef --- /dev/null +++ b/src/engine/net/client/msg/ReqBankInventoryMsg.java @@ -0,0 +1,87 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Bank inventory requested + * + * @author Burfo + */ +public class ReqBankInventoryMsg extends ClientNetMsg { + + private int playerType; + private int playerID; + private long unknown01; // possibly NPC ID? + + /** + * This is the general purpose constructor. + */ + public ReqBankInventoryMsg(AbstractGameObject ago, long unknown01) { + super(Protocol.OKCOSTTOOPENBANK); + this.playerType = ago.getObjectType().ordinal(); + this.playerID = ago.getObjectUUID(); + this.unknown01 = unknown01; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ReqBankInventoryMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.OKCOSTTOOPENBANK, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + playerType = reader.getInt(); + playerID = reader.getInt(); + unknown01 = reader.getLong(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(playerType); + writer.putInt(playerID); + writer.putLong(unknown01); + } + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } + +} diff --git a/src/engine/net/client/msg/RequestBallListMessage.java b/src/engine/net/client/msg/RequestBallListMessage.java new file mode 100644 index 00000000..bbdd4365 --- /dev/null +++ b/src/engine/net/client/msg/RequestBallListMessage.java @@ -0,0 +1,71 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class RequestBallListMessage extends ClientNetMsg { + + public static int REQUEST = 0; + public int playerID; + public String errorMessage; + public int msgType; + + /** + * This is the general purpose constructor. + */ + public RequestBallListMessage(PlayerCharacter pc) { + super(Protocol.REQUESTBALLLIST); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RequestBallListMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTBALLLIST, origin, reader); + } + + /** + * Copy constructor + */ + public RequestBallListMessage(RequestBallListMessage msg) { + super(Protocol.REQUESTBALLLIST); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + this.playerID = reader.getInt(); + this.msgType = reader.getInt(); + this.errorMessage = reader.getString(); + + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.playerID); + writer.putInt(this.msgType); + writer.putString(this.errorMessage); + } +} diff --git a/src/engine/net/client/msg/RequestEnterWorldMsg.java b/src/engine/net/client/msg/RequestEnterWorldMsg.java new file mode 100644 index 00000000..217eb375 --- /dev/null +++ b/src/engine/net/client/msg/RequestEnterWorldMsg.java @@ -0,0 +1,70 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class RequestEnterWorldMsg extends ClientNetMsg { + + private byte pad; + + /** + * This is the general purpose constructor. + */ + public RequestEnterWorldMsg() { + super(Protocol.ENTERWORLD); + this.pad = 0x00; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RequestEnterWorldMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ENTERWORLD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.put(this.pad); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.pad = reader.get(); + } + + /** + * @return the pad + */ + public byte getPad() { + return pad; + } + + /** + * @param pad the pad to set + */ + public void setPad(byte pad) { + this.pad = pad; + } + +} diff --git a/src/engine/net/client/msg/RespawnMsg.java b/src/engine/net/client/msg/RespawnMsg.java new file mode 100644 index 00000000..ebf5eaa5 --- /dev/null +++ b/src/engine/net/client/msg/RespawnMsg.java @@ -0,0 +1,100 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class RespawnMsg extends ClientNetMsg { + + protected int objectType; + protected int objectID; + protected float playerHealth; + protected int playerExp; + + /** + * This is the general purpose constructor. + */ + public RespawnMsg() { + super(Protocol.RESETAFTERDEATH); + this.objectType = 0; + this.objectID = 0; + this.playerHealth = 0; + this.playerExp = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RespawnMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.RESETAFTERDEATH, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.objectType); + writer.putInt(this.objectID); + writer.putFloat(this.playerHealth); + writer.putInt(this.playerExp); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + this.objectType = reader.getInt(); + this.objectID = reader.getInt(); + this.playerHealth = reader.getFloat(); + this.playerExp = reader.getInt(); + } + + public int getObjectType() { + return this.objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getObjectID() { + return this.objectID; + } + + public void setObjectID(int value) { + this.objectID = value; + } + + public float getPlayerHealth() { + return this.playerHealth; + } + + public void setPlayerHealth(float value) { + this.playerHealth = value; + } + + public int getPlayerExp() { + return this.playerExp; + } + + public void setPlayerExp(int value) { + this.playerExp = value; + } +} diff --git a/src/engine/net/client/msg/RespondLeaveWorldMsg.java b/src/engine/net/client/msg/RespondLeaveWorldMsg.java new file mode 100644 index 00000000..a2c646dc --- /dev/null +++ b/src/engine/net/client/msg/RespondLeaveWorldMsg.java @@ -0,0 +1,52 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class RespondLeaveWorldMsg extends ClientNetMsg { + + /** + * This is the general purpose constructor. + */ + public RespondLeaveWorldMsg() { + super(Protocol.LEAVEWORLD); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RespondLeaveWorldMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LEAVEWORLD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + +} diff --git a/src/engine/net/client/msg/RotateObjectMsg.java b/src/engine/net/client/msg/RotateObjectMsg.java new file mode 100644 index 00000000..090a9b8c --- /dev/null +++ b/src/engine/net/client/msg/RotateObjectMsg.java @@ -0,0 +1,60 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Mob; + +public class RotateObjectMsg extends ClientNetMsg { + + + /** + * This is the general purpose constructor. + */ + public RotateObjectMsg(int type, Mob pet) { + super(Protocol.ROTATEMSG); + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RotateObjectMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ROTATEMSG, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getFloat(); + reader.getFloat(); + reader.getFloat(); + reader.getInt(); + } +} diff --git a/src/engine/net/client/msg/SafeModeMsg.java b/src/engine/net/client/msg/SafeModeMsg.java new file mode 100644 index 00000000..8dfb9de5 --- /dev/null +++ b/src/engine/net/client/msg/SafeModeMsg.java @@ -0,0 +1,72 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class SafeModeMsg extends ClientNetMsg { + + private String message; + + /** + * Helper Constructor. + *

+ * This is the general purpose constructor. + */ + public SafeModeMsg() { + this("Safe Mode"); + } + + /** + * This is the general purpose constructor. + */ + public SafeModeMsg(String message) { + super(Protocol.SAFEMODE); + this.message = message; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SafeModeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SAFEMODE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.message); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.message = reader.getString(); + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } +} diff --git a/src/engine/net/client/msg/ScaleObjectMsg.java b/src/engine/net/client/msg/ScaleObjectMsg.java new file mode 100644 index 00000000..877fbcef --- /dev/null +++ b/src/engine/net/client/msg/ScaleObjectMsg.java @@ -0,0 +1,99 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ScaleObjectMsg extends ClientNetMsg { + + private long compID; + private float scaleX; + private float scaleY; + private float scaleZ; + + /** + * This is the general purpose constructor. + */ + public ScaleObjectMsg(long compID, float scaleX, float scaleY, float scaleZ) { + super(Protocol.SCALEOBJECT); + this.compID = compID; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ScaleObjectMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SCALEOBJECT, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putLong(this.compID); + writer.putFloat(this.scaleX); + writer.putFloat(this.scaleY); + writer.putFloat(this.scaleZ); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.compID = reader.getLong(); + this.scaleX = reader.getFloat(); + this.scaleY = reader.getFloat(); + this.scaleZ = reader.getFloat(); + } + + public long getCompID() { + return this.compID; + } + + public void setCompID(long value) { + this.compID = value; + } + + public float getScaleX() { + return this.scaleX; + } + + public void setScaleX(float value) { + this.scaleX = value; + } + + public float getScaleY() { + return this.scaleY; + } + + public void setScaleY(float value) { + this.scaleY = value; + } + + public float getScaleZ() { + return this.scaleZ; + } + + public void setScaleZ(float value) { + this.scaleZ = value; + } +} diff --git a/src/engine/net/client/msg/SelectCityMsg.java b/src/engine/net/client/msg/SelectCityMsg.java new file mode 100644 index 00000000..4e13af87 --- /dev/null +++ b/src/engine/net/client/msg/SelectCityMsg.java @@ -0,0 +1,94 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class SelectCityMsg extends ClientNetMsg { + + private PlayerCharacter pc; + private boolean isTeleport; + + /** + * This is the general purpose constructor. + */ + public SelectCityMsg(PlayerCharacter pc, boolean isTeleport) { + super(Protocol.SELECTCITY); + this.pc = pc; + this.isTeleport = isTeleport; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SelectCityMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SELECTCITY, origin, reader); + } + + /** + * Copy constructor + */ + public SelectCityMsg(SelectCityMsg msg) { + super(Protocol.SELECTCITY); + this.pc = msg.pc; + this.isTeleport = msg.isTeleport; + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (12); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //Do we even want to try this? + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.put((byte) 0); + } + + public PlayerCharacter pc() { + return this.pc; + } + + public boolean isTeleport() { + return this.isTeleport; + } + + public void setPC(PlayerCharacter pc) { + this.pc = pc; + } + + public void setIsTeleport(boolean value) { + this.isTeleport = value; + } +} diff --git a/src/engine/net/client/msg/SellToNPCMsg.java b/src/engine/net/client/msg/SellToNPCMsg.java new file mode 100644 index 00000000..dba25dba --- /dev/null +++ b/src/engine/net/client/msg/SellToNPCMsg.java @@ -0,0 +1,113 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +/** + * Buy from NPC msg + * + * @author Eighty + */ +public class SellToNPCMsg extends ClientNetMsg { + + int npcType; + int npcID; + int itemType; + int itemID; + int unknown01; + + /** + * This is the general purpose constructor + */ + public SellToNPCMsg() { + super(Protocol.SELLOBJECT); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SellToNPCMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SELLOBJECT, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.itemType = reader.getInt(); + this.itemID = reader.getInt(); + this.unknown01 = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(this.npcType); + writer.putInt(this.npcID); + writer.putInt(this.itemType); + writer.putInt(this.itemID); + writer.putInt(this.unknown01); + } + + public int getNPCType() { + return this.npcType; + } + + public void setNPCType(int value) { + this.npcType = value; + } + + public int getNPCID() { + return this.npcID; + } + + public void setNPCID(int value) { + this.npcID = value; + } + + public int getItemType() { + return this.itemType; + } + + public void setItemType(int value) { + this.itemType = value; + } + + public int getItemID() { + return this.itemID; + } + + public void setItemID(int value) { + this.itemID = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } +} diff --git a/src/engine/net/client/msg/SellToNPCWindowMsg.java b/src/engine/net/client/msg/SellToNPCWindowMsg.java new file mode 100644 index 00000000..17e37139 --- /dev/null +++ b/src/engine/net/client/msg/SellToNPCWindowMsg.java @@ -0,0 +1,287 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +import java.util.ArrayList; + + +/** + * Sell to NPC window msg + * + * @author Eighty + */ +public class SellToNPCWindowMsg extends ClientNetMsg { + +//Item Types: +//1: weapon +//2: armor/cloth/shield +//5: scrolls +//8: potions +//10: charters +//13: Jewelry + + + private int npcType; + private int npcID; + private byte unknownByte01; //so far always 0x00 + private byte unknownByte02; //0: show only specified, 1: show all + private ArrayList itemTypes; + private ArrayList skillTokens; + private ArrayList unknownArray02; + private int unknown01; //so far always 0 + private int unknown02; //so far always 0 on output + private int unknown03; //so far always 10000 + private int unknown04; //so far always 0 on output + private float unknown05; //suspect sell percentage, ex: 0.26 for 26% + private int unknown06; //suspect gold available on vendor + private int unknown07; //so far always 0 + + /** + * This is the general purpose constructor + */ + public SellToNPCWindowMsg(int npcType, int npcID, byte unknownByte02, + ArrayList itemTypes, ArrayList skillTokens, + ArrayList unknownArray02, float unknown05, int unknown06) { + super(Protocol.SHOPINFO); + this.npcType = npcType; + this.npcID = npcID; + this.unknownByte01 = (byte) 0; + this.unknownByte02 = unknownByte02; + this.itemTypes = itemTypes; + this.skillTokens = skillTokens; + this.unknownArray02 = unknownArray02; + this.unknown01 = 0; + this.unknown02 = 0; + this.unknown03 = 10000; + this.unknown04 = 0; + this.unknown05 = unknown05; + this.unknown06 = unknown06; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public SellToNPCWindowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SHOPINFO, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + this.itemTypes = new ArrayList<>(); + this.skillTokens = new ArrayList<>(); + this.unknownArray02 = new ArrayList<>(); + + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.unknownByte01 = reader.get(); + this.unknownByte02 = reader.get(); + int cnt = reader.getInt(); + for (int i = 0; i < cnt; i++) + this.itemTypes.add(reader.getInt()); + cnt = reader.getInt(); + for (int i = 0; i < cnt; i++) + this.skillTokens.add(reader.getInt()); + cnt = reader.getInt(); + for (int i = 0; i < cnt; i++) + this.unknownArray02.add(reader.getInt()); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getFloat(); + this.unknown06 = reader.getInt(); + this.unknown07 = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(this.npcType); + writer.putInt(this.npcID); + writer.put(this.unknownByte01); + writer.put(this.unknownByte02); + writer.putInt(this.itemTypes.size()); + for (Integer i : this.itemTypes) + writer.putInt(i); + writer.putInt(this.skillTokens.size()); + for (Integer i : this.skillTokens) + writer.putInt(i); + writer.putInt(this.unknownArray02.size()); + for (Integer i : this.unknownArray02) + writer.putInt(i); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putFloat(this.unknown05); + writer.putInt(this.unknown06); + writer.putInt(this.unknown07); + } + + public int getNPCType() { + return this.npcType; + } + + public void setNPCType(int value) { + this.npcType = value; + } + + public int getNPCID() { + return this.npcID; + } + + public void setNPCID(int value) { + this.npcID = value; + } + + public byte getUnknownByte01() { + return this.unknownByte01; + } + + public void setUnknownByte01(byte value) { + this.unknownByte01 = value; + } + + public byte getUnknownByte02() { + return this.unknownByte02; + } + + public void setUnknownByte02(byte value) { + this.unknownByte02 = value; + } + + public ArrayList getItemTypes() { + return this.itemTypes; + } + + public void setItemTypes(ArrayList value) { + this.itemTypes = value; + } + + public ArrayList getSkillTokens() { + return this.skillTokens; + } + + public void setSkillTokens(ArrayList value) { + this.skillTokens = value; + } + + public ArrayList getUnknownArray02() { + return this.unknownArray02; + } + + public void setUnknownArray02(ArrayList value) { + this.unknownArray02 = value; + } + + public int getUnknown01() { + return unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getUnknown02() { + return unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getUnknown03() { + return unknown03; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public int getUnknown04() { + return unknown04; + } + + public void setUnknown04(int value) { + this.unknown04 = value; + } + + public float getUnknown05() { + return unknown05; + } + + public void setUnknown05(float value) { + this.unknown05 = value; + } + + public int getUnknown06() { + return unknown06; + } + + public void setUnknown06(int value) { + this.unknown06 = value; + } + + public int getUnknown07() { + return unknown07; + } + + public void setUnknown07(int value) { + this.unknown07 = value; + } + + public void setskillTokens(ArrayList value) { + this.skillTokens = value; + } + + public void addItemType(int value) { + this.itemTypes.add(value); + } + + public void addSkillToken(int value) { + this.skillTokens.add(value); + } + + public void addUnknownArray02(int value) { + this.unknownArray02.add(value); + } + + public void setItemType(ArrayList value) { + this.itemTypes = value; + } + + public void setUnknownArray(ArrayList value) { + this.unknownArray02 = value; + } + + public void setupOutput() { + this.unknownByte01 = (byte) 0; + this.unknownByte02 = (byte) 0; + this.unknown01 = 0; + this.unknown02 = 0; + this.unknown03 = 10000; + this.unknown04 = 0; + this.unknown07 = 0; + } +} diff --git a/src/engine/net/client/msg/SendBallEntryMessage.java b/src/engine/net/client/msg/SendBallEntryMessage.java new file mode 100644 index 00000000..9b431841 --- /dev/null +++ b/src/engine/net/client/msg/SendBallEntryMessage.java @@ -0,0 +1,106 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class SendBallEntryMessage extends ClientNetMsg { + + public static final int ADDBALL = 4; + public static final int WHITEBALL = 2; + public static final int BLACKBALL = 1; + public int playerID; + public String description; + public int msgType; + public int ballColor; + + /** + * This is the general purpose constructor. + */ + public SendBallEntryMessage(PlayerCharacter pc) { + super(Protocol.SENDBALLENTRY); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SendBallEntryMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SENDBALLENTRY, origin, reader); + } + + /** + * Copy constructor + */ + public SendBallEntryMessage(SendBallEntryMessage msg) { + super(Protocol.SENDBALLENTRY); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.msgType = reader.getInt(); + + switch (this.msgType) { + case ADDBALL: + this.readAddBall(reader); + break; + default: + break; + } + + } + + public void readAddBall(ByteBufferReader reader) { + reader.getInt(); + this.playerID = reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); // 1 ? + + reader.getInt(); + reader.getInt(); + + reader.getInt(); // source player type + reader.getInt(); // source player ID + + reader.getInt(); // targetType + reader.getInt(); // targetID (same as this.playerID) + this.description = reader.getString(); + reader.get(); + reader.get(); + reader.getInt(); // 100, max ball mark + reader.get(); + reader.get(); + reader.get(); + this.ballColor = reader.getInt(); + reader.get(); + + + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + } +} diff --git a/src/engine/net/client/msg/SendOwnPlayerMsg.java b/src/engine/net/client/msg/SendOwnPlayerMsg.java new file mode 100644 index 00000000..f83f0dc1 --- /dev/null +++ b/src/engine/net/client/msg/SendOwnPlayerMsg.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import engine.objects.Regions; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +public class SendOwnPlayerMsg extends ClientNetMsg { + + private PlayerCharacter ch; + + /** + * This is the general purpose constructor. + * + * @param s Session from which the PlayerCharacter is obtained + */ + public SendOwnPlayerMsg(Session s) { + super(Protocol.PLAYERDATA); + + // Get all the character records for this account + ch = s.getPlayerCharacter(); + } + + /** + * This is the general purpose constructor. + * + * @param pc Playercharacter + */ + public SendOwnPlayerMsg(PlayerCharacter pc) { + super(Protocol.PLAYERDATA); + this.ch = pc; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SendOwnPlayerMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.PLAYERDATA, origin, reader); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + //Larger size for historically larger opcodes + return (17); // 2^17 == 131,072 + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + Regions region = this.ch.region; + //region loading seralization. serialzes building level and floor. -1 = not in building. + if (region == null) { + writer.putInt(-1); + writer.putInt(-1); + } else { + Building regionBuilding = Regions.GetBuildingForRegion(region); + if (regionBuilding == null) { + writer.putInt(-1); + writer.putInt(-1); + } else { + writer.putInt(region.getLevel()); + writer.putInt(region.getRoom()); + } + } + writer.putVector3f(ch.getLoc()); + try { + PlayerCharacter.serializeForClientMsgFull(this.ch, writer); + } catch (SerializationException e) { + Logger.error(e); + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + int unknown1 = reader.getInt(); + int unknown2 = reader.getInt(); + + int unknown3 = reader.getInt(); + int unknown4 = reader.getInt(); + + int unknown5 = reader.getInt(); + int unknown6 = reader.getInt(); + int unknown7 = reader.getInt(); + + // TODO finish deserialization implementation. + } + + public PlayerCharacter getChar() { + return this.ch; + } +} diff --git a/src/engine/net/client/msg/SendSummonsRequestMsg.java b/src/engine/net/client/msg/SendSummonsRequestMsg.java new file mode 100644 index 00000000..a634343e --- /dev/null +++ b/src/engine/net/client/msg/SendSummonsRequestMsg.java @@ -0,0 +1,106 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class SendSummonsRequestMsg extends ClientNetMsg { + + private int powerToken; + private int sourceType; + private int sourceID; + private String targetName; + private int trains; + + /** + * This is the general purpose constructor. + */ + public SendSummonsRequestMsg() { + super(Protocol.POWERTARGNAME); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SendSummonsRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.POWERTARGNAME, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.powerToken); + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putString(this.targetName); + writer.putInt(this.trains); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.powerToken = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetName = reader.getString(); + this.trains = reader.getInt(); + } + + public int getPowerToken() { + return this.powerToken; + } + + public void setPowerToken(int value) { + this.powerToken = value; + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public String getTargetName() { + return this.targetName; + } + + public void setTargetName(String value) { + this.targetName = value; + } + + public int getTrains() { + return this.trains; + } + + public void setTrains(int value) { + this.trains = value; + } +} diff --git a/src/engine/net/client/msg/ServerInfoMsg.java b/src/engine/net/client/msg/ServerInfoMsg.java new file mode 100644 index 00000000..33320c0f --- /dev/null +++ b/src/engine/net/client/msg/ServerInfoMsg.java @@ -0,0 +1,84 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.gameManager.ConfigManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.server.MBServerStatics; +import engine.server.login.LoginServer; +import engine.server.world.WorldServer; + + +public class ServerInfoMsg extends ClientNetMsg { + + + /** + * This is the general purpose constructor. + */ + public ServerInfoMsg() { + super(Protocol.SELECTSERVER); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ServerInfoMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SELECTSERVER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + // writer.putInt(this.servers.size()); + // for (WorldServerInfoSnapshot wsis : this.servers) { + // wsis.serializeForClientMsg(writer); + // } + writer.putInt(1); + + writer.putInt(WorldServer.worldMapID); + writer.putString(ConfigManager.MB_WORLD_NAME.getValue()); + if (LoginServer.population < MBServerStatics.LOW_POPULATION) + writer.putInt(0); //Land Rush + else if (LoginServer.population < MBServerStatics.NORMAL_POPULATION) + writer.putInt(1); //Low pop + else if (LoginServer.population < MBServerStatics.HIGH_POPULATION) + writer.putInt(2); //Normal pop + else if (LoginServer.population < MBServerStatics.VERY_OVERPOPULATED_POPULATION) + writer.putInt(3); //High Pop + else if (LoginServer.population < MBServerStatics.FULL_POPULATION) + writer.putInt(4); //Very overpopulated pop + else + writer.putInt(5); //Full pop + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + int size = reader.getInt(); + for (int i = 0; i < size; i++) { + int ID = reader.getInt(); + String name = reader.getString(); + int pop = reader.getInt(); + } + } + + +} diff --git a/src/engine/net/client/msg/SetCombatModeMsg.java b/src/engine/net/client/msg/SetCombatModeMsg.java new file mode 100644 index 00000000..7ae0f3f5 --- /dev/null +++ b/src/engine/net/client/msg/SetCombatModeMsg.java @@ -0,0 +1,93 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +/** + * Attack from outside of combat mode. + * + * @author Eighty + */ +public class SetCombatModeMsg extends ClientNetMsg { + + private long playerCompID; + private boolean toggle; + + /** + * This is the general purpose constructor. + */ + public SetCombatModeMsg(long playerCompID, boolean toggle) { + super(Protocol.ARCCOMBATMODEATTACKING); + this.playerCompID = playerCompID; + this.toggle = toggle; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SetCombatModeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCCOMBATMODEATTACKING, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putLong(playerCompID); + writer.put(toggle ? (byte) 0x01 : (byte) 0x00); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.playerCompID = reader.getLong(); + this.toggle = (reader.get() == 0x01) ? true : false; + } + + /** + * @return the playerCompID + */ + public long getPlayerCompID() { + return playerCompID; + } + + /** + * @param playerCompID the playerCompID to set + */ + public void setPlayerCompID(long playerCompID) { + this.playerCompID = playerCompID; + } + + /** + * @return the toggle + */ + public boolean getToggle() { + return toggle; + } + + /** + * @param toggle the toggle to set + */ + public void setToggle(boolean toggle) { + this.toggle = toggle; + } +} diff --git a/src/engine/net/client/msg/SetObjectValueMsg.java b/src/engine/net/client/msg/SetObjectValueMsg.java new file mode 100644 index 00000000..a7ea9e68 --- /dev/null +++ b/src/engine/net/client/msg/SetObjectValueMsg.java @@ -0,0 +1,98 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +public class SetObjectValueMsg extends ClientNetMsg { + private int targetType; + private int targetID; + private int msgType; + private AbstractGameObject ago; + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SetObjectValueMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SETOBJVAL, origin, reader); + } + + public SetObjectValueMsg() { + super(Protocol.SETOBJVAL); + } + + public SetObjectValueMsg(AbstractGameObject ago, int type) { + super(Protocol.SETOBJVAL); + if (ago == null) + return; + this.msgType = type; + this.targetType = ago.getObjectType().ordinal(); + this.targetID = ago.getObjectUUID(); + this.ago = ago; + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(msgType); + writer.putInt(0); + } + + + public AbstractGameObject getAgo() { + return ago; + } + + public void setAgo(AbstractGameObject ago) { + this.ago = ago; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + +} diff --git a/src/engine/net/client/msg/ShowBankInventoryMsg.java b/src/engine/net/client/msg/ShowBankInventoryMsg.java new file mode 100644 index 00000000..cbf1ee58 --- /dev/null +++ b/src/engine/net/client/msg/ShowBankInventoryMsg.java @@ -0,0 +1,115 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.Item; +import engine.objects.PlayerCharacter; + +import java.util.ArrayList; + + +/** + * Bank inventory contents + * + * @author Burfo + */ +public class ShowBankInventoryMsg extends ClientNetMsg { + + PlayerCharacter pc; + long unknown01; + + /** + * This is the general purpose constructor. + */ + public ShowBankInventoryMsg(PlayerCharacter pc, long unknown01) { + super(Protocol.BANKINVENTORY); + this.pc = pc; + this.unknown01 = unknown01; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ShowBankInventoryMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.BANKINVENTORY, origin, reader); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + ArrayList bank = pc.getCharItemManager().getBank(); + + writer.put((byte) 1); // static value + Item.putList(writer, bank, false, pc.getObjectUUID()); + writer.putInt(AbstractCharacter.getBankCapacity()); + + // TODO: Gold is sent last and has a slightly different structure. + // Everything is static except the 3 labeled lines + //TODO: f/Eighty: I don't think gold is sent separately. + // will need to check once transfer to bank is working. + /* + 00:00:00:00: + 07:00:00:00: + 00:00:20:0A:5C:46:29:14: comp id + 00:00:00:00:00:00:00:00:00:00:00:00: + 00:00:80:3F:00:00:80:3F:00:00:80:3F:00:00:80:3F: + 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00: + FF:FF:FF:FF:FF:FF:FF:FF: + 00:00:00:00: + 01: + 00:00:00:00: + 01: + 00:00:00:00:00:00:00:00: + 01: + 00:00:00:00:00:00:00:00: + 9C:1B:04:00 quantity of gold? + 00:00:00:00: + 00:00:00:00: + 00:00:00:00: + 04:00:00:00: + 00:00:00:00: + 00:00:00:00: + 01:00:00:00: + 00:00: + 00: + 58:02:00:00: unknown? + */ + + writer.putInt(pc.getObjectType().ordinal()); + writer.putInt(pc.getObjectUUID()); + writer.putLong(unknown01); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return 17; // 2^15 == 32,768 + } +} diff --git a/src/engine/net/client/msg/ShowMsg.java b/src/engine/net/client/msg/ShowMsg.java new file mode 100644 index 00000000..93686ce8 --- /dev/null +++ b/src/engine/net/client/msg/ShowMsg.java @@ -0,0 +1,140 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.math.Vector3fImmutable; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ShowMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + private Vector3fImmutable unknown01; + private Vector3fImmutable unknown02; + private float range01; + private Vector3fImmutable unknown03; + private Vector3fImmutable unknown04; + private float range02; + + /** + * This is the general purpose constructor. + */ + public ShowMsg() { + super(Protocol.SHOWCOMBATINFO); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ShowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SHOWCOMBATINFO, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putVector3f(this.unknown01); + writer.putVector3f(this.unknown02); + writer.putFloat(this.range01); + writer.putVector3f(this.unknown03); + writer.putVector3f(this.unknown04); + writer.putFloat(this.range02); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.unknown01 = reader.getVector3fImmutable(); + this.unknown02 = reader.getVector3fImmutable(); + this.range01 = reader.getFloat(); + this.unknown03 = reader.getVector3fImmutable(); + this.unknown04 = reader.getVector3fImmutable(); + this.range02 = reader.getFloat(); + } + + public int getTargetType() { + return this.targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + public int getTargetID() { + return this.targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public Vector3fImmutable getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(Vector3fImmutable value) { + this.unknown01 = value; + } + + public Vector3fImmutable getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(Vector3fImmutable value) { + this.unknown02 = value; + } + + public Vector3fImmutable getUnknown03() { + return this.unknown03; + } + + public void setUnknown03(Vector3fImmutable value) { + this.unknown03 = value; + } + + public Vector3fImmutable getUnknown04() { + return this.unknown04; + } + + public void setUnknown04(Vector3fImmutable value) { + this.unknown04 = value; + } + + public float getRange01() { + return this.range01; + } + + public void setRange01(float value) { + this.range01 = value; + } + + public float getRange02() { + return this.range02; + } + + public void setRange02(float value) { + this.range02 = value; + } +} diff --git a/src/engine/net/client/msg/ShowVaultInventoryMsg.java b/src/engine/net/client/msg/ShowVaultInventoryMsg.java new file mode 100644 index 00000000..5df29795 --- /dev/null +++ b/src/engine/net/client/msg/ShowVaultInventoryMsg.java @@ -0,0 +1,87 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; + +import java.util.ArrayList; + +/** + * Vault inventory contents + * + * @author Eighty + */ +public class ShowVaultInventoryMsg extends ClientNetMsg { + + PlayerCharacter pc; + int accountType; + int accountID; + + int npcType; + int npcID; + + + /** + * This is the general purpose constructor. + */ + public ShowVaultInventoryMsg(PlayerCharacter pc, Account account, NPC npc) { + super(Protocol.SHOWVAULTINVENTORY); + this.pc = pc; + this.accountType = account.getObjectType().ordinal(); + this.accountID = account.getObjectUUID(); + this.npcType = npc.getObjectType().ordinal(); + this.npcID = npc.getObjectUUID(); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ShowVaultInventoryMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.SHOWVAULTINVENTORY, origin, reader); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + + writer.putInt(accountType); + writer.putInt(accountID); + writer.putInt(npcType); + writer.putInt(npcID); + writer.putString(pc.getFirstName()); + + ArrayList vault = pc.getAccount().getVault(); + + Item.putList(writer, vault, false, pc.getObjectUUID()); + writer.putInt(AbstractCharacter.getVaultCapacity()); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + @Override + protected int getPowerOfTwoBufferSize() { + return 17; + } +} diff --git a/src/engine/net/client/msg/SocialMsg.java b/src/engine/net/client/msg/SocialMsg.java new file mode 100644 index 00000000..be748357 --- /dev/null +++ b/src/engine/net/client/msg/SocialMsg.java @@ -0,0 +1,171 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class SocialMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private int unknown01; + private int unknown02; + private int targetType; + private int targetID; + private int social; + + /** + * This is the general purpose constructor. + */ + public SocialMsg() { + super(Protocol.SOCIALCHANNEL); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SocialMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SOCIALCHANNEL, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.social); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.social = reader.getInt(); + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + /** + * @param sourceType the sourceType to set + */ + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + /** + * @param sourceID the sourceID to set + */ + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @param targetType the targetType to set + */ + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + /** + * @param targetID the targetID to set + */ + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + /** + * @return the social + */ + public int getSocial() { + return social; + } + + /** + * @param social the social to set + */ + public void setSocial(int social) { + this.social = social; + } + +} diff --git a/src/engine/net/client/msg/StuckCommandMsg.java b/src/engine/net/client/msg/StuckCommandMsg.java new file mode 100644 index 00000000..2058633e --- /dev/null +++ b/src/engine/net/client/msg/StuckCommandMsg.java @@ -0,0 +1,52 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class StuckCommandMsg extends ClientNetMsg { + + /** + * This is the general purpose constructor. + */ + public StuckCommandMsg() { + super(Protocol.STUCK); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public StuckCommandMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.STUCK, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + +} diff --git a/src/engine/net/client/msg/SyncMessage.java b/src/engine/net/client/msg/SyncMessage.java new file mode 100644 index 00000000..dfe5a150 --- /dev/null +++ b/src/engine/net/client/msg/SyncMessage.java @@ -0,0 +1,151 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; +import engine.objects.Zone; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +public class SyncMessage extends ClientNetMsg { + + private int type; + private int size; + private int pad = 0; + private int objectType; + private int objectUUID; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public SyncMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CITYASSET, origin, reader); + } + + public SyncMessage() { + super(Protocol.CITYASSET); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //none yet + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + //lets do returns before writing so we don't send improper structures to the client + + Building tol = BuildingManager.getBuilding(this.objectUUID); + + if (tol == null) { + Logger.debug("TOL is null"); + return; + } + Zone zone = ZoneManager.findSmallestZone(tol.getLoc()); + if (zone == null) { + Logger.debug("Zone is null"); + return; + } + ArrayList allCityAssets = DbManager.BuildingQueries.GET_ALL_BUILDINGS_FOR_ZONE(zone); + + // *** Refactor: collection created but never used? + + ArrayList canProtectAssets = new ArrayList<>(); + + for (Building b : allCityAssets) { + if (b.getBlueprintUUID() != 0) + canProtectAssets.add(b); + } + + // *** Refactor : Not sure what this synch message does + // Get the feeling it should be looping over upgradable + // assets. + writer.putInt(0); + writer.putInt(0); + writer.putInt(this.objectType); + writer.putInt(this.objectUUID); + writer.putInt(allCityAssets.size()); + for (Building b : allCityAssets) { + String name = b.getName(); + // if (name.equals("")) + // name = b.getBuildingSet().getName(); + writer.putInt(b.getObjectType().ordinal()); + writer.putInt(b.getObjectUUID()); + + writer.putString(b.getName()); // Blueprint name? + writer.putString(b.getGuild().getName()); + writer.putInt(20);// \/ Temp \/ + writer.putInt(b.getRank()); + writer.putInt(1); // symbol + writer.putInt(7); //TODO identify these Guild tags?? + writer.putInt(17); + writer.putInt(14); + writer.putInt(14); + writer.putInt(98);// /\ Temp /\ + + } + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getUUID() { + return objectUUID; + + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } +} diff --git a/src/engine/net/client/msg/TargetObjectMsg.java b/src/engine/net/client/msg/TargetObjectMsg.java new file mode 100644 index 00000000..8c823da7 --- /dev/null +++ b/src/engine/net/client/msg/TargetObjectMsg.java @@ -0,0 +1,81 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class TargetObjectMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + + /** + * This is the general purpose constructor. + */ + public TargetObjectMsg(int targetType, int targetID) { + super(Protocol.SETSELECTEDOBECT); + this.targetType = targetType; + this.targetID = targetID; + } + + /** + * This is the general purpose constructor. + */ + public TargetObjectMsg() { + super(Protocol.SETSELECTEDOBECT); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TargetObjectMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SETSELECTEDOBECT, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + +} diff --git a/src/engine/net/client/msg/TargetedActionMsg.java b/src/engine/net/client/msg/TargetedActionMsg.java new file mode 100644 index 00000000..c60d7056 --- /dev/null +++ b/src/engine/net/client/msg/TargetedActionMsg.java @@ -0,0 +1,334 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerCharacter; + +public class TargetedActionMsg extends ClientNetMsg { + + public static int un2cnt = 65; + + //attack animations + //64: overhead RH swing 1h RH axe? + //65: overhead LH swing + //66: underhand RH uppercut + //67: shoulder high RH swing + //68: underhand RH swing + //69: sweeping LH swing + //70: overhead circle RH swing + //71: RH across body and back swing + //72: RH 1h overhead to 2h swing + //73: RH overhead to cross body swing (bm?) + //74: 2h low stab to cross body slash + //75: unarmed punch unarmed RH + //76: unarmed RH punch LH punch + //77: unarmed LH jab + //78: unarmed LH jab, RH uppercut + //79: kick + //80: roundhouse kick + //81: dagger RH stab dagger RH + //82: dagger LH stab + //83: dagger slash + //84: dagger hard stab + //85: Polearm/staff overhead swing Polearm, Staff + //86: Polearm/staff side swing + //87: Polearm/staff step into overhead swing + //88: swinging RH stab + //89: swinging LF stab + //90: swinging RH stab (faster) + //91: 1H slash across body and back (sword?) + //92: spear stab spear + //93: spear low stab step into + //94: spear swing leg stab + //95: unarmed overhead swing RH, underhand swing LH + //96: inverted weapon across body followed by roundhouse LH swing + //97: step back followed by overhead roundhouse swing 2H + //98: underhand slash (1h sword) 1H RH Sword + //99: fast LH swing (dagger or sword?) + //100: 1h swing RH (sword) 1h axe? + //101: 1h overhead swing (club or sword) + //102: fast 1h underhand swing (club or sword) + //103: step into RH slash 1h + //104: 1h overhead to cross body slash RH + //105: 2h overhead swing (axe, hammer, sword) 2H Axe, Hammer, Sword + //106: step into 2h swing + //107: step int 2h overhead swing + //108: step into 2h swing + //109: bow draw and fire bow + //110: crossbow draw and fire crossbow + //115: throwing axe/hammer? + //116: overhand throwing dagger? + //117: throwing dagger + + private int sourceType; + private int sourceID; + private int targetType; + private int targetID; + private float locX; + private float locZ; + private int unknown01 = 14; + private int unknown02 = 100; //source animation + private float unknown03 = 1f; + private float sourceStamina = 1f; // attackers stamina after attack + private int unknown05 = 6; //signify passive defense + private int unknown06 = 10; //target animation + private float newHealth = 10f; // health after damage + private float damage = 0f; // damage, 0 for miss + + /** + * This is the general purpose constructor. + */ + public TargetedActionMsg(int sourceType, int sourceID, int targetType, int targetID, float locX, float locZ, float sourceStamina, + float newHealth, float damage) { + super(Protocol.TARGETEDACTION); + this.sourceType = sourceType; + this.sourceID = sourceID; + this.targetType = targetType; + this.targetID = targetID; + this.sourceStamina = sourceStamina; + this.locX = locX; + this.locZ = locZ; + this.newHealth = newHealth; + this.damage = damage; + //this.unknown02 = TargetedActionMsg.un2cnt; + } + + /** + * This is a helper constructor. Designed to send an UPDATE only. Damage for + * this constructor is hard coded to ZERO. + */ + public TargetedActionMsg(PlayerCharacter pc) { + super(Protocol.TARGETEDACTION); + this.sourceType = pc.getObjectType().ordinal(); + this.sourceID = pc.getObjectUUID(); + this.targetType = pc.getObjectType().ordinal(); + this.targetID = pc.getObjectUUID(); + this.sourceStamina = pc.getStamina(); + this.locX = pc.getLoc().x; + this.locZ = pc.getLoc().z; + this.newHealth = pc.getCurrentHitpoints(); + this.damage = 0.0f; + } + + public TargetedActionMsg(PlayerCharacter pc, int unknown06) { + super(Protocol.TARGETEDACTION); + this.sourceType = pc.getObjectType().ordinal(); + this.sourceID = pc.getObjectUUID(); + this.targetType = pc.getObjectType().ordinal(); + this.targetID = pc.getObjectUUID(); + this.sourceStamina = pc.getStamina(); + this.locX = pc.getLoc().x; + this.locZ = pc.getLoc().z; + this.newHealth = pc.getCurrentHitpoints(); + this.damage = 0.0f; + } + + public TargetedActionMsg(AbstractCharacter source, AbstractWorldObject target, Float damage, int swingAnimation) { + super(Protocol.TARGETEDACTION); + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + this.sourceStamina = source.getStamina(); + } else { + this.sourceType = 0; + this.sourceID = 0; + this.sourceStamina = 0; + } + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.locX = target.getLoc().x; + this.locZ = target.getLoc().z; + this.newHealth = target.getHealth(); + this.damage = damage; + + } else { + this.targetType = 0; + this.targetID = 0; + this.locX = 50000f; + this.locZ = -50000f; + this.newHealth = 1f; + this.damage = damage; + } + this.unknown02 = swingAnimation; + //this.unknown02 = TargetedActionMsg.un2cnt; + } + + public TargetedActionMsg(AbstractCharacter source, AbstractWorldObject target, Float damage, int swingAnimation, int dead) { + super(Protocol.TARGETEDACTION); + if (source != null) { + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + this.sourceStamina = source.getStamina(); + } else { + this.sourceType = 0; + this.sourceID = 0; + this.sourceStamina = 0; + } + if (target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.locX = target.getLoc().x; + this.locZ = target.getLoc().z; + this.newHealth = target.getCurrentHitpoints(); + this.damage = damage; + this.unknown06 = dead; + + } else { + this.targetType = 0; + this.targetID = 0; + this.locX = 50000f; + this.locZ = -50000f; + this.newHealth = 1f; + this.damage = damage; + } + this.unknown02 = swingAnimation; + //this.unknown02 = TargetedActionMsg.un2cnt; + } + + /** + * Added in an attempt to have mobs fall over after death. + */ + + public TargetedActionMsg(AbstractWorldObject target, boolean kill) { + this(null, target, 0f, 75); + if (kill) { + this.newHealth = -1f; + this.sourceType = 0x101; + this.sourceID = 0x101; + } + } + + /** + * This constructor can be used to create CombatMessages that indicate a block or parry has occurred.
+ *
+ * Set passiveAnimation to 21 for block.
+ * Set passiveAnimation to 22 for parry.
+ */ + public TargetedActionMsg(AbstractCharacter source, int swingAnimation, AbstractWorldObject target, int passiveAnimation) { + this(source, target, 0.0f, swingAnimation); + this.unknown05 = passiveAnimation; + this.unknown06 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TargetedActionMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TARGETEDACTION, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putFloat(this.locX); + writer.putFloat(this.locZ); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putFloat(this.unknown03); + writer.putFloat(this.sourceStamina); + writer.putInt(this.unknown05); + // writer.putInt(this.unknown06); + + if (this.newHealth < 0) + writer.putInt(55); + else if (damage != 0 && this.unknown05 < 20) + writer.putInt(60); + else + writer.putInt(this.unknown06); + writer.putFloat(this.newHealth); + writer.putFloat(this.damage); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.locX = reader.getFloat(); + this.locZ = reader.getFloat(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getFloat(); + this.sourceStamina = reader.getFloat(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.getInt(); + this.newHealth = reader.getFloat(); + this.damage = reader.getFloat(); + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + public void setTargetType(int value) { + this.targetType = value; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + public void setTargetID(int value) { + this.targetID = value; + } + + public float getDamage() { + return damage; + } +} diff --git a/src/engine/net/client/msg/TaxCityMsg.java b/src/engine/net/client/msg/TaxCityMsg.java new file mode 100644 index 00000000..a00a4041 --- /dev/null +++ b/src/engine/net/client/msg/TaxCityMsg.java @@ -0,0 +1,114 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.gameManager.BuildingManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; +import engine.objects.GuildTag; + + +public class TaxCityMsg extends ClientNetMsg { + + + private int buildingID; + private int msgType; + + + public TaxCityMsg(Building building, int msgType) { + super(Protocol.TAXCITY); + this.buildingID = building.getObjectUUID(); + this.msgType = msgType; + + } + + public TaxCityMsg() { + super(Protocol.TAXCITY); + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TaxCityMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TAXCITY, origin, reader); + } + //CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD. + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.msgType = reader.getInt(); + reader.getInt(); //object Type.. always building + this.buildingID = reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + } + + + // Precache and configure this message before we serialize it + + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putInt(0); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + Building building = BuildingManager.getBuildingFromCache(this.buildingID); + + writer.putInt(0); + writer.putFloat(.2f); + GuildTag._serializeForDisplay(building.getGuild().getGuildTag(), writer); + GuildTag._serializeForDisplay(building.getGuild().getGuildTag(), writer); + + + } + + public int getGuildID() { + return buildingID; + } + + public int getMsgType() { + return msgType; + } + + public void setMsgType(int msgType) { + this.msgType = msgType; + } + +} diff --git a/src/engine/net/client/msg/TaxResourcesMsg.java b/src/engine/net/client/msg/TaxResourcesMsg.java new file mode 100644 index 00000000..dd8ea199 --- /dev/null +++ b/src/engine/net/client/msg/TaxResourcesMsg.java @@ -0,0 +1,123 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Building; + +import java.util.HashMap; + + +public class TaxResourcesMsg extends ClientNetMsg { + + + private int buildingID; + private int msgType; + private HashMap resources; + private float taxPercent; + + + public TaxResourcesMsg(Building building, int msgType) { + super(Protocol.TAXRESOURCES); + this.buildingID = building.getObjectUUID(); + this.msgType = msgType; + + } + + public TaxResourcesMsg() { + super(Protocol.TAXRESOURCES); + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TaxResourcesMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TAXRESOURCES, origin, reader); + } + //CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD. + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.msgType = reader.getInt(); + reader.getInt(); //object Type.. always building + this.buildingID = reader.getInt(); + HashMap resourcesTemp = new HashMap<>(); + int size = reader.getInt(); + for (int i = 0; i < size; i++) { + int resourceHash = reader.getInt(); + resourcesTemp.put(resourceHash, 0); + } + this.resources = resourcesTemp; + taxPercent = reader.getFloat(); + reader.getInt(); + + + } + + + // Precache and configure this message before we serialize it + + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putInt(0); + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(this.buildingID); + writer.putInt(this.resources.size()); + for (int resource : resources.keySet()) + writer.putInt(resource); + writer.putFloat(this.taxPercent); + writer.putInt(this.resources.size()); + for (int resource : resources.keySet()) { + writer.putInt(resource); + writer.putInt(0); + writer.putInt(resources.get(resource)); + } + + } + + public int getBuildingID() { + return buildingID; + } + + public int getMsgType() { + return msgType; + } + + public void setMsgType(int msgType) { + this.msgType = msgType; + } + + public HashMap getResources() { + return resources; + } + + public float getTaxPercent() { + return taxPercent; + } + +} diff --git a/src/engine/net/client/msg/TeleportRepledgeListMsg.java b/src/engine/net/client/msg/TeleportRepledgeListMsg.java new file mode 100644 index 00000000..a76f2abf --- /dev/null +++ b/src/engine/net/client/msg/TeleportRepledgeListMsg.java @@ -0,0 +1,113 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.City; +import engine.objects.PlayerCharacter; + +import java.util.ArrayList; + + +public class TeleportRepledgeListMsg extends ClientNetMsg { + + ArrayList cities; + private PlayerCharacter player; + private boolean isTeleport; + + /** + * This is the general purpose constructor. + */ + public TeleportRepledgeListMsg(PlayerCharacter player, boolean isTeleport) { + super(Protocol.SENDCITYENTRY); + this.player = player; + this.isTeleport = isTeleport; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TeleportRepledgeListMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SENDCITYENTRY, origin, reader); + } + + /** + * Copy constructor + */ + public TeleportRepledgeListMsg(TeleportRepledgeListMsg msg) { + super(Protocol.SENDCITYENTRY); + this.player = msg.player; + this.isTeleport = msg.isTeleport; + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (16); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + //Do we even want to try this? + } + + // Pre-caches and configures message so data is avaiable + // when we serialize. + + public void configure() { + + if (isTeleport) + cities = City.getCitiesToTeleportTo(player); + else + cities = City.getCitiesToRepledgeTo(player); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + if (isTeleport) + writer.putInt(2); //teleport? + else + writer.putInt(1); //repledge? + + for (int i = 0; i < 3; i++) + writer.putInt(0); + + writer.putInt(cities.size()); + + for (City city : cities) + City.serializeForClientMsg(city, writer); + } + + public PlayerCharacter getPlayer() { + return this.player; + } + + public boolean isTeleport() { + return this.isTeleport; + } + +} diff --git a/src/engine/net/client/msg/TeleportToPointMsg.java b/src/engine/net/client/msg/TeleportToPointMsg.java new file mode 100644 index 00000000..07b4a1fe --- /dev/null +++ b/src/engine/net/client/msg/TeleportToPointMsg.java @@ -0,0 +1,215 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class TeleportToPointMsg extends ClientNetMsg { + + private int sourceType; + private int sourceUUID; + private float endLat; + private float endLon; + private float endAlt; + private int targetType; + private int targetUUID; + private int unknown01; + private int unknown02; + + /** + * This is the general purpose constructor. + */ + public TeleportToPointMsg(AbstractWorldObject ago, float endLat, float endAlt, float endLon, long targetID, int unknown01, int unknown02) { + super(Protocol.TELEPORT); + + this.sourceType = ago.getObjectType().ordinal(); + this.sourceUUID = ago.getObjectUUID(); + this.endLat = endLat; + this.endAlt = endAlt; + this.endLon = endLon; + if (targetID != 0) { + this.targetType = GameObjectType.Building.ordinal(); + this.targetUUID = (int) targetID; + } else { + this.targetType = 0; + this.targetUUID = 0; + } + + + this.unknown01 = unknown01; + this.unknown02 = unknown02; + + if (ago.region != null) { + this.targetType = GameObjectType.Building.ordinal(); + this.targetUUID = ago.region.parentBuildingID; + this.unknown01 = ago.region.level; + this.unknown02 = ago.region.room; + } + } + + /** + * This is the general purpose constructor. + */ + public TeleportToPointMsg() { + super(Protocol.TELEPORT); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TeleportToPointMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TELEPORT, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceUUID); + writer.putFloat(this.endLat); + writer.putFloat(this.endAlt); + writer.putFloat(this.endLon); + writer.putInt(this.targetType); + writer.putInt(this.targetUUID); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceUUID = reader.getInt(); + this.endLat = reader.getInt(); + this.endAlt = reader.getInt(); + this.endLon = reader.getInt(); + this.targetType = reader.getInt(); + this.targetUUID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + } + + + /** + * @return the endLat + */ + public float getEndLat() { + return endLat; + } + + /** + * @param endLat the endLat to set + */ + public void setEndLat(float endLat) { + this.endLat = endLat; + } + + /** + * @return the endLon + */ + public float getEndLon() { + return endLon; + } + + /** + * @param endLon the endLon to set + */ + public void setEndLon(float endLon) { + this.endLon = endLon; + } + + /** + * @return the endAlt + */ + public float getEndAlt() { + return endAlt; + } + + /** + * @param endAlt the endAlt to set + */ + public void setEndAlt(float endAlt) { + this.endAlt = endAlt; + } + + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + public int getSourceUUID() { + return sourceUUID; + } + + public void setSourceUUID(int sourceUUID) { + this.sourceUUID = sourceUUID; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + public int getTargetUUID() { + return targetUUID; + } + + public void setTargetUUID(int targetUUID) { + this.targetUUID = targetUUID; + } + +} diff --git a/src/engine/net/client/msg/TerritoryChangeMessage.java b/src/engine/net/client/msg/TerritoryChangeMessage.java new file mode 100644 index 00000000..87e811b0 --- /dev/null +++ b/src/engine/net/client/msg/TerritoryChangeMessage.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; +import engine.objects.Realm; + +public class TerritoryChangeMessage extends ClientNetMsg { + + + private Realm realm; + private PlayerCharacter realmOwner; + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TerritoryChangeMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TERRITORYCHANGE, origin, reader); + } + + public TerritoryChangeMessage() { + super(Protocol.TERRITORYCHANGE); + } + + public TerritoryChangeMessage(PlayerCharacter guildLeader, Realm realm) { + super(Protocol.TERRITORYCHANGE); + + this.realm = realm; + this.realmOwner = guildLeader; + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + writer.putString(realm.getRealmName()); + if (this.realmOwner != null) { + writer.putString(this.realmOwner.getCombinedName()); + writer.putInt(PlayerCharacter.GetPlayerRealmTitle(this.realmOwner)); + writer.putInt(1); + writer.put((byte) 1); + writer.put((byte) 1); + writer.putInt(realm.getCharterType()); + if (this.realmOwner != null && this.realmOwner.getGuild() != null) + writer.putString(this.realmOwner.getGuild().getName()); + else + writer.putString("None"); + writer.put((byte) 0); + } else { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); + writer.put((byte) 1); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + } + + + } + + +} diff --git a/src/engine/net/client/msg/ToggleCombatMsg.java b/src/engine/net/client/msg/ToggleCombatMsg.java new file mode 100644 index 00000000..613ee1f0 --- /dev/null +++ b/src/engine/net/client/msg/ToggleCombatMsg.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class ToggleCombatMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private boolean toggleCombat; + + /** + * This is the general purpose constructor. + */ + public ToggleCombatMsg() { + super(Protocol.COMBATMODE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ToggleCombatMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.COMBATMODE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.put(this.toggleCombat ? (byte) 0x01 : (byte) 0x00); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.toggleCombat = (reader.get() == (byte) 0x01) ? true : false; + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public boolean toggleCombat() { + return this.toggleCombat; + } + + public void setToggleCombat(boolean value) { + this.toggleCombat = value; + } +} diff --git a/src/engine/net/client/msg/ToggleLfgRecruitingMsg.java b/src/engine/net/client/msg/ToggleLfgRecruitingMsg.java new file mode 100644 index 00000000..8b443240 --- /dev/null +++ b/src/engine/net/client/msg/ToggleLfgRecruitingMsg.java @@ -0,0 +1,96 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ToggleLfgRecruitingMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private byte toggleLfgRecruiting; + private byte unknown01; + + /** + * This is the general purpose constructor. + */ + public ToggleLfgRecruitingMsg() { + super(Protocol.MODIFYGUILDSTATE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ToggleLfgRecruitingMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MODIFYGUILDSTATE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.put(this.toggleLfgRecruiting); + writer.put(this.unknown01); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.toggleLfgRecruiting = reader.get(); + this.unknown01 = reader.get(); + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public byte toggleLfgRecruiting() { + return this.toggleLfgRecruiting; + } + + public byte getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(byte value) { + this.unknown01 = value; + } + + public void setToggleLfgRecruiting(byte value) { + this.toggleLfgRecruiting = value; + } + +} diff --git a/src/engine/net/client/msg/ToggleSitStandMsg.java b/src/engine/net/client/msg/ToggleSitStandMsg.java new file mode 100644 index 00000000..e7e94898 --- /dev/null +++ b/src/engine/net/client/msg/ToggleSitStandMsg.java @@ -0,0 +1,84 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class ToggleSitStandMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private boolean toggleSitStand; + + /** + * This is the general purpose constructor. + */ + public ToggleSitStandMsg() { + super(Protocol.TOGGLESITSTAND); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ToggleSitStandMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TOGGLESITSTAND, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.put(this.toggleSitStand ? (byte) 0x01 : (byte) 0x00); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.toggleSitStand = (reader.get() == (byte) 0x01) ? true : false; + } + + public int getSourceType() { + return this.sourceType; + } + + public void setSourceType(int value) { + this.sourceType = value; + } + + public int getSourceID() { + return this.sourceID; + } + + public void setSourceID(int value) { + this.sourceID = value; + } + + public boolean toggleSitStand() { + return this.toggleSitStand; + } + + public void setToggleSitStand(boolean value) { + this.toggleSitStand = value; + } +} diff --git a/src/engine/net/client/msg/TrackArrowMsg.java b/src/engine/net/client/msg/TrackArrowMsg.java new file mode 100644 index 00000000..b66056de --- /dev/null +++ b/src/engine/net/client/msg/TrackArrowMsg.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class TrackArrowMsg extends ClientNetMsg { + + protected float direction; + + public TrackArrowMsg(float direction) { + super(Protocol.ARCTRACKOBJECT); + this.direction = direction; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TrackArrowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCTRACKOBJECT, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putFloat(this.direction); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.direction = reader.getFloat(); + } + + public float getDirection() { + return this.direction; + } + + public void setDirection(float value) { + this.direction = value; + } +} diff --git a/src/engine/net/client/msg/TrackWindowMsg.java b/src/engine/net/client/msg/TrackWindowMsg.java new file mode 100644 index 00000000..7b647ee9 --- /dev/null +++ b/src/engine/net/client/msg/TrackWindowMsg.java @@ -0,0 +1,149 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; + +import java.util.HashSet; + + +public class TrackWindowMsg extends ClientNetMsg { + + private int powerToken; + private PlayerCharacter source = null; + private HashSet characters = new HashSet<>(); + + /** + * This is the general purpose constructor. + */ + public TrackWindowMsg(int powerToken, HashSet characters) { + super(Protocol.ARCTRACKINGLIST); + this.powerToken = powerToken; + this.characters = characters; + } + + public TrackWindowMsg(TrackWindowMsg trackWindowMsg) { + super(Protocol.ARCTRACKINGLIST); + this.powerToken = trackWindowMsg.powerToken; + this.source = trackWindowMsg.source; + this.characters = trackWindowMsg.characters; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TrackWindowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCTRACKINGLIST, origin, reader); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (13); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.powerToken); + writer.putInt(characters.size()); + for (AbstractCharacter ac : characters) { + boolean isGroup = false; + if (this.source != null && ac.getObjectType().equals(GameObjectType.PlayerCharacter)) { + if (Group.sameGroup((PlayerCharacter) ac, this.source)) + isGroup = true; + } + AbstractCharacter.serializeForTrack(ac, writer, isGroup); + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.powerToken = reader.getInt(); + + int size = reader.getInt(); + for (int i = 0; i < size; i++) { + int objectType = reader.getInt(); + int objectID = reader.getInt(); + this.source = PlayerCharacter.getFromCache(objectID); + reader.getString(); //name + reader.get(); //always 00? + reader.getInt(); //guildObjectType + reader.getInt(); //guildID + reader.get(); //always 01? + for (int j = 0; j < 5; j++) + reader.getInt(); //guild tags + reader.getInt(); //nation ObjectType + reader.getInt(); //nation ID + reader.get(); //always 01? + for (int j = 0; j < 5; j++) + reader.getInt(); //nation tags + + //Get the Character from it's Object Type and ID + AbstractCharacter ac = null; + if (objectType == GameObjectType.PlayerCharacter.ordinal()) + ac = PlayerCharacter.getFromCache(objectID); + else if (objectType == GameObjectType.NPC.ordinal()) + ac = NPC.getFromCache(objectID); + else if (objectType == GameObjectType.Mob.ordinal()) + ac = Mob.getFromCache(objectID); + + //If found, add to message list + if (ac != null) + characters.add(ac); + } + } + + public int getPowerToken() { + return this.powerToken; + } + + public void setPowerToken(int value) { + this.powerToken = value; + } + + public HashSet getCharacters() { + return this.characters; + } + + public void setCharacters(HashSet value) { + this.characters = value; + } + + public void addCharacter(PlayerCharacter value) { + if (value != null) + this.characters.add(value); + } + + public void clearChracters() { + this.characters.clear(); + } + + public void setSource(PlayerCharacter value) { + this.source = value; + } +} diff --git a/src/engine/net/client/msg/TradeRequestMsg.java b/src/engine/net/client/msg/TradeRequestMsg.java new file mode 100644 index 00000000..bf9ace62 --- /dev/null +++ b/src/engine/net/client/msg/TradeRequestMsg.java @@ -0,0 +1,128 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +/** + * Request trade + * + * @author Eighty + */ +public class TradeRequestMsg extends ClientNetMsg { + + private int unknown01; //pad? + private int playerType; + private int playerID; + private int sourceType; + private int sourceID; + + + /** + * This is the general purpose constructor + */ + public TradeRequestMsg(int unknown01, AbstractGameObject player, AbstractGameObject target) { + super(Protocol.REQUESTTOTRADE); + this.unknown01 = unknown01; + this.sourceType = player.getObjectType().ordinal(); + this.sourceID = player.getObjectUUID(); + this.playerType = target.getObjectType().ordinal(); + this.playerID = target.getObjectUUID(); + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public TradeRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTTOTRADE, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.playerType = reader.getInt(); + this.playerID = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.playerType); + writer.putInt(this.playerID); + + + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } + + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + +} diff --git a/src/engine/net/client/msg/TrainMsg.java b/src/engine/net/client/msg/TrainMsg.java new file mode 100644 index 00000000..cb778dcf --- /dev/null +++ b/src/engine/net/client/msg/TrainMsg.java @@ -0,0 +1,355 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.Enum.ProtectionState; +import engine.exception.MsgSendException; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.gameManager.SessionManager; +import engine.net.*; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.*; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.concurrent.ConcurrentHashMap; + + +public class TrainMsg extends ClientNetMsg { + + private int npcType; + private int npcID; + private int unknown01 = 1; + private int trainCost01; //why two trainer costs? + private int trainCost02; //why two trainer costs? + private boolean isSkill; //true: skill; false: power + private int token; + private boolean unknown02 = true; + private String ok = ""; + private int unknown03 = 0; + + /** + * This is the general purpose constructor. + */ + public TrainMsg() { + super(Protocol.TRAINSKILL); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TrainMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRAINSKILL, origin, reader); + } + + public static void train(TrainMsg msg, ClientConnection origin) throws MsgSendException { + origin.getPlayerCharacter().recalculate(); + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin); + Dispatch dispatch; + + if (playerCharacter == null) + return; + + NPC npc = NPC.getFromCache(msg.npcID); + + if (npc == null) + return; + + if (origin.trainLock.tryLock()) { + try { + Item gold = playerCharacter.getCharItemManager().getGoldInventory(); + + if (gold == null) + return; + + if (!gold.validForInventory(origin, playerCharacter, playerCharacter.getCharItemManager())) + return; + + boolean canTrain = false; + if (msg.isSkill) { + + //Get skill + SkillsBase sb = DbManager.SkillsBaseQueries.GET_BASE_BY_TOKEN(msg.token); + ConcurrentHashMap skills = playerCharacter.getSkills(); + + if (sb == null || skills == null) + return; + + CharacterSkill sk = skills.get(sb.getName()); + + if (sk == null) + return; + + if (sk.getSkillsBase().getToken() == 40661438) { + int maxValue = 15; + + + if (MaxSkills.MaxSkillsSet.get(252647) != null) + for (MaxSkills maxSkills : MaxSkills.MaxSkillsSet.get(252647)) { + if (maxSkills.getSkillToken() != sk.getToken()) + continue; + + if (maxSkills.getSkillLevel() > npc.getLevel()) + continue; + maxValue += maxSkills.getMaxSkillPercent(); + } + if (maxValue > sk.getModifiedAmountBeforeMods()) { + canTrain = true; + } + + } + if (canTrain == false) + if (npc.getContract() != null && npc.getContract().getExtraRune() != 0) { + int maxValue = 15; + + + if (MaxSkills.MaxSkillsSet.get(npc.getContract().getExtraRune()) != null) + for (MaxSkills maxSkills : MaxSkills.MaxSkillsSet.get(npc.getContract().getExtraRune())) { + if (maxSkills.getSkillToken() != sk.getToken()) + continue; + + if (maxSkills.getSkillLevel() > npc.getLevel()) + continue; + maxValue += maxSkills.getMaxSkillPercent(); + } + if (maxValue > sk.getModifiedAmountBeforeMods()) { + canTrain = true; + } + + + } + if (canTrain == false) { + int maxValue = 15; + if (MaxSkills.MaxSkillsSet.get(npc.getContractID()) != null) + for (MaxSkills maxSkills : MaxSkills.MaxSkillsSet.get(npc.getContractID())) { + if (maxSkills.getSkillToken() != sk.getToken()) + continue; + + if (maxSkills.getSkillLevel() > npc.getLevel()) + continue; + maxValue += maxSkills.getMaxSkillPercent(); + } + if (maxValue > sk.getModifiedAmountBeforeMods()) { + canTrain = true; + } + } + + if (canTrain == false) { + int maxValue = 15; + if (MaxSkills.MaxSkillsSet.get(npc.getContract().getClassID()) != null) + for (MaxSkills maxSkills : MaxSkills.MaxSkillsSet.get(npc.getContract().getClassID())) { + if (maxSkills.getSkillToken() != sk.getToken()) + continue; + + if (maxSkills.getSkillLevel() > npc.getLevel()) + continue; + maxValue += maxSkills.getMaxSkillPercent(); + } + if (maxValue > sk.getModifiedAmountBeforeMods()) { + canTrain = true; + } + } + + if (canTrain == false) { + int maxValue = 15; + if (MaxSkills.MaxSkillsSet.get(npc.extraRune2) != null) + for (MaxSkills maxSkills : MaxSkills.MaxSkillsSet.get(npc.getContract().getClassID())) { + if (maxSkills.getSkillToken() != sk.getToken()) + continue; + + if (maxSkills.getSkillLevel() > npc.getLevel()) + continue; + maxValue += maxSkills.getMaxSkillPercent(); + } + if (maxValue > sk.getModifiedAmountBeforeMods()) { + canTrain = true; + } + } + + if (canTrain == false) { + ChatManager.chatSystemError(playerCharacter, "NPC cannot train that skill any higher"); + return; + } + + float cost = sk.getTrainingCost(playerCharacter, npc); + float profitCost = cost * npc.getSellPercent(playerCharacter); + + profitCost += .5f; + if (profitCost > playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems()) + return; + Building b = npc.getBuilding(); + if (b != null && b.getProtectionState().equals(ProtectionState.NPC)) + b = null; + + if (b != null && b.getStrongboxValue() + (profitCost - cost) > b.getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(playerCharacter, 206); + return; + } + + if (playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems() - profitCost < 0) + return; + + if (playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems() - profitCost > MBServerStatics.PLAYER_GOLD_LIMIT) + return; + + + //attempt to train + if (sk.train(playerCharacter)) { + playerCharacter.getCharItemManager().buyFromNPC(b, (int) profitCost, (int) (profitCost - cost)); + + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + //update trainer window + + if (npc != null) { + TrainerInfoMsg tim = new TrainerInfoMsg(msg.npcType, msg.npcID, npc.getSellPercent(playerCharacter)); + tim.setTrainPercent(npc.getSellPercent(playerCharacter)); + dispatch = Dispatch.borrow(playerCharacter, tim); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + } + + } else { + //Get Power + int token = msg.token; + + if (MBServerStatics.POWERS_DEBUG) { + ChatManager.chatSayInfo(playerCharacter, "Training Power: " + + Integer.toHexString(msg.token) + " (" + msg.token + ')'); + System.out.println("Training Power: " + + Integer.toHexString(msg.token) + " (" + msg.token + ')'); + } + + PowersBase pb = PowersManager.getPowerByToken(token); + ConcurrentHashMap powers = playerCharacter.getPowers(); + if (pb == null || powers == null) + return; + + if (pb.isWeaponPower) + return; + CharacterPower cp = null; + if (powers.containsKey(token)) + cp = powers.get(token); + if (cp == null) + return; + + //attempt to train + float cost = (int) cp.getTrainingCost(playerCharacter, npc); + float profitCost = cost * npc.getSellPercent(playerCharacter); + profitCost += .5f; + if (profitCost > playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems()) { + // ChatManager.chatSystemError(pc, "You do not have enough gold to train this skill."); + return; + } + + Building b = npc.getBuilding(); + + if (b != null && b.getProtectionState().equals(ProtectionState.NPC)) + b = null; + + if (b != null && b.getStrongboxValue() + (profitCost - cost) > b.getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(playerCharacter, 206); + return; + } + if (cp.train(playerCharacter)) { + + if (!playerCharacter.getCharItemManager().buyFromNPC(b, (int) profitCost, (int) (profitCost - cost))) + ChatManager.chatSystemError(playerCharacter, "Failed to Withdrawl gold from inventory. Contact CCR"); + + //train succeeded + + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + //update trainer window + + if (npc != null) { + TrainerInfoMsg tim = new TrainerInfoMsg(msg.npcType, msg.npcID, npc.getSellPercent(playerCharacter)); + tim.setTrainPercent(npc.getSellPercent(playerCharacter)); + dispatch = Dispatch.borrow(playerCharacter, tim); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + } + } + } catch (Exception e) { + Logger.error(e); + } finally { + origin.trainLock.unlock(); + } + + + } + + + } + + public static float getTrainPercent(NPC npc) { + return 0f; + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.npcType); + writer.putInt(this.npcID); + writer.putInt(this.unknown01); + writer.putInt(trainCost01); + writer.putInt(trainCost02); + writer.put((this.isSkill == true) ? (byte) 0x01 : (byte) 0x00); + writer.putInt(this.token); + writer.put((this.unknown02 == true) ? (byte) 0x01 : (byte) 0x00); + writer.putString(this.ok); + writer.putInt(this.unknown03); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.npcType = reader.getInt(); + this.npcID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.trainCost01 = reader.getInt(); + this.trainCost02 = reader.getInt(); + this.isSkill = (reader.get() == (byte) 0x01) ? true : false; + this.token = reader.getInt(); + this.unknown02 = (reader.get() == (byte) 0x01) ? true : false; + this.ok = reader.getString(); + this.unknown03 = reader.getInt(); + } + + public int getToken() { + return this.token; + } + + public boolean isSkill() { + return this.isSkill; + } + + public int getNpcType() { + return this.npcType; + } + + public int getNpcID() { + return this.npcID; + } +} diff --git a/src/engine/net/client/msg/TrainerInfoMsg.java b/src/engine/net/client/msg/TrainerInfoMsg.java new file mode 100644 index 00000000..ea8c5630 --- /dev/null +++ b/src/engine/net/client/msg/TrainerInfoMsg.java @@ -0,0 +1,101 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class TrainerInfoMsg extends ClientNetMsg { + + private int objectType; + private int objectID; + private float trainPercent; + + /** + * This is the general purpose constructor. + */ + public TrainerInfoMsg(int objectType, int objectID, float trainPercent) { + super(Protocol.TRAINERLIST); + this.objectType = objectType; + this.objectID = objectID; + this.trainPercent = trainPercent; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TrainerInfoMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRAINERLIST, origin, reader); + } + + /** + * Copy constructor + */ + public TrainerInfoMsg(TrainerInfoMsg msg) { + super(Protocol.TRAINERLIST); + this.objectType = msg.objectType; + this.objectID = msg.objectID; + this.trainPercent = msg.trainPercent; + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.get(); + this.objectType = reader.getInt(); + this.objectID = reader.getInt(); + this.trainPercent = reader.getFloat(); + reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.put((byte) 0); + writer.putInt(this.objectType); + writer.putInt(this.objectID); + writer.putFloat(this.trainPercent); + writer.putInt(0); + } + + public int getObjectType() { + return this.objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getObjectID() { + return this.objectID; + } + + public void setObjectID(int value) { + this.objectID = value; + } + + public float getTrainPercent() { + return this.trainPercent; + } + + public void setTrainPercent(float value) { + this.trainPercent = value; + } +} diff --git a/src/engine/net/client/msg/TransferAssetMsg.java b/src/engine/net/client/msg/TransferAssetMsg.java new file mode 100644 index 00000000..1ea61d0a --- /dev/null +++ b/src/engine/net/client/msg/TransferAssetMsg.java @@ -0,0 +1,105 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class TransferAssetMsg extends ClientNetMsg { + + private int objectType; + private int objectID; + private int targetType; + private int targetID; + private int pad = 0; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferAssetMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRANSFERASSET, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.pad = reader.getInt(); + this.objectType = reader.getInt(); + this.objectID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + reader.getShort(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.pad); + writer.putInt(this.objectType); + writer.putInt(this.objectID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putShort((short) 0); + + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetObject(int targetObject) { + this.targetType = targetObject; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + public int getObjectID() { + return objectID; + } + + public void setObjectID(int objectID) { + this.objectID = objectID; + } + + +} \ No newline at end of file diff --git a/src/engine/net/client/msg/TransferBuildingMsg.java b/src/engine/net/client/msg/TransferBuildingMsg.java new file mode 100644 index 00000000..7a04c25c --- /dev/null +++ b/src/engine/net/client/msg/TransferBuildingMsg.java @@ -0,0 +1,103 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class TransferBuildingMsg extends ClientNetMsg { + + private int objectType; + private int objectID; + private int targetType; + private int targetID; + private int pad = 0; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferBuildingMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRANSFERASSET, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.pad = reader.getInt(); + this.objectType = reader.getInt(); + this.objectID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.pad); + writer.putInt(this.objectType); + writer.putInt(this.objectID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getTargetType() { + return targetType; + } + + public void setTargetObject(int targetObject) { + this.targetType = targetObject; + } + + public int getTargetID() { + return targetID; + } + + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + public int getObjectID() { + return objectID; + } + + public void setObjectID(int objectID) { + this.objectID = objectID; + } + + +} \ No newline at end of file diff --git a/src/engine/net/client/msg/TransferGoldFromInventoryToVaultMsg.java b/src/engine/net/client/msg/TransferGoldFromInventoryToVaultMsg.java new file mode 100644 index 00000000..459bc61a --- /dev/null +++ b/src/engine/net/client/msg/TransferGoldFromInventoryToVaultMsg.java @@ -0,0 +1,104 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +/** + * Transfer gold from inventory to vault + * + * @author Eighty + */ + +public class TransferGoldFromInventoryToVaultMsg extends ClientNetMsg { + + private int playerID; + private int accountID; + private int npcID; + + private int amount; + + /** + * This is the general purpose constructor + */ + public TransferGoldFromInventoryToVaultMsg() { + super(Protocol.GOLDTOVAULT); + + } + + public TransferGoldFromInventoryToVaultMsg(int playerID, int npcID, int accountID, int amount) { + super(Protocol.GOLDTOVAULT); + this.playerID = playerID; + this.npcID = npcID; + this.accountID = accountID; + this.amount = amount; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferGoldFromInventoryToVaultMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GOLDTOVAULT, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + this.playerID = reader.getInt(); + reader.getInt(); + this.accountID = reader.getInt(); + reader.getInt(); + this.npcID = reader.getInt(); + this.amount = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.playerID); + writer.putInt(GameObjectType.Account.ordinal()); + writer.putInt(this.accountID); + writer.putInt(GameObjectType.NPC.ordinal()); + writer.putInt(this.npcID); + + writer.putInt(this.amount); + } + + + /** + * @return the amount + */ + public int getAmount() { + return amount; + } + + /** + * @param amount the amount to set + */ + public void setAmount(int amount) { + this.amount = amount; + } + +} diff --git a/src/engine/net/client/msg/TransferGoldFromVaultToInventoryMsg.java b/src/engine/net/client/msg/TransferGoldFromVaultToInventoryMsg.java new file mode 100644 index 00000000..b76b68f6 --- /dev/null +++ b/src/engine/net/client/msg/TransferGoldFromVaultToInventoryMsg.java @@ -0,0 +1,130 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +/** + * Transfer gold from vault to inventory + * + * @author Eighty + */ +public class TransferGoldFromVaultToInventoryMsg extends ClientNetMsg { + + private long unknown01; + private long playerCompID; + private long accountCompID; + private int amount; + + /** + * This is the general purpose constructor + */ + public TransferGoldFromVaultToInventoryMsg(long unknown01, long playerCompID, long accountCompID, int amount) { + super(Protocol.TRANSFERGOLDFROMVAULTTOINVENTORY); + this.unknown01 = unknown01; + this.playerCompID = playerCompID; + this.accountCompID = accountCompID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferGoldFromVaultToInventoryMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRANSFERGOLDFROMVAULTTOINVENTORY, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getLong(); + playerCompID = reader.getLong(); + accountCompID = reader.getLong(); + amount = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putLong(unknown01); + writer.putLong(playerCompID); + writer.putLong(accountCompID); + writer.putInt(amount); + } + + /** + * @return the unknown01 + */ + public long getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(long unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the playerCompID + */ + public long getPlayerCompID() { + return playerCompID; + } + + /** + * @param playerCompID the playerCompID to set + */ + public void setPlayerCompID(long playerCompID) { + this.playerCompID = playerCompID; + } + + /** + * @return the unknown02 + */ + public long getAccountCompID() { + return accountCompID; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setAccountCompID(long accountCompID) { + this.accountCompID = accountCompID; + } + + /** + * @return the amount + */ + public int getAmount() { + return amount; + } + + /** + * @param amount the amount to set + */ + public void setAmount(int amount) { + this.amount = amount; + } + +} diff --git a/src/engine/net/client/msg/TransferGoldToFromBuildingMsg.java b/src/engine/net/client/msg/TransferGoldToFromBuildingMsg.java new file mode 100644 index 00000000..60011884 --- /dev/null +++ b/src/engine/net/client/msg/TransferGoldToFromBuildingMsg.java @@ -0,0 +1,145 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +/** + * Transfer gold to/from building strongbox/reserve + * + * @author Eighty + */ + +public class TransferGoldToFromBuildingMsg extends ClientNetMsg { + + private int direction; //Maybe? 1 and 2 + private int failReason; + private int objectType; + private int objectID; + private int amount; + private int unknown01; + private int unknown02; + + /** + * This is the general purpose constructor + */ + public TransferGoldToFromBuildingMsg() { + super(Protocol.TRANSFERGOLDTOFROMBUILDING); + this.direction = 0; + this.failReason = 0; + this.objectType = 0; + this.objectID = 0; + this.amount = 0; + this.unknown01 = 0; + this.unknown02 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferGoldToFromBuildingMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRANSFERGOLDTOFROMBUILDING, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.direction = reader.getInt(); + this.failReason = reader.getInt(); + this.objectType = reader.getInt(); + this.objectID = reader.getInt(); + this.amount = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(this.direction); + writer.putInt(this.failReason); + writer.putInt(this.objectType); + writer.putInt(this.objectID); + writer.putInt(this.amount); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + + } + + public int getDirection() { + return this.direction; + } + + public void setDirection(int value) { + this.direction = value; + } + + public int getAmount() { + return this.amount; + } + + public void setAmount(int value) { + this.amount = value; + } + + public int getUnknown01() { + return this.unknown01; + } + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getUnknown02() { + return this.unknown02; + } + + public void setUnknown02(int value) { + this.unknown02 = value; + } + + public int getFailReason() { + return failReason; + } + + public void setFailReason(int failReason) { + this.failReason = failReason; + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int objectType) { + this.objectType = objectType; + } + + public int getObjectID() { + return objectID; + } + + public void setObjectID(int objectID) { + this.objectID = objectID; + } +} diff --git a/src/engine/net/client/msg/TransferItemFromBankToInventoryMsg.java b/src/engine/net/client/msg/TransferItemFromBankToInventoryMsg.java new file mode 100644 index 00000000..cd29a156 --- /dev/null +++ b/src/engine/net/client/msg/TransferItemFromBankToInventoryMsg.java @@ -0,0 +1,219 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +/** + * Transfer item from bank to inventory + * + * @author Eighty + */ + +public class TransferItemFromBankToInventoryMsg extends ClientNetMsg { + + private long playerCompID1; + private long playerCompID2; + private int type; + private int objectUUID; + private int unknown1; + private int unknown2; + private int numItems; + private byte unknown4; + + /** + * This is the general purpose constructor + */ + public TransferItemFromBankToInventoryMsg(long playerCompID1, + long playerCompID2, int type, int objectUUID, int unknown1, + int unknown2, int numItems, byte unknown4) { + super(Protocol.TRANSFERITEMFROMBANK); + this.playerCompID1 = playerCompID1; + this.playerCompID2 = playerCompID2; + this.type = type; + this.objectUUID = objectUUID; + this.unknown1 = unknown1; + this.unknown2 = unknown2; + this.numItems = numItems; + this.unknown4 = unknown4; + } + + public TransferItemFromBankToInventoryMsg(TransferItemFromInventoryToBankMsg msg) { + super(Protocol.TRANSFERITEMFROMBANK); + this.playerCompID1 = msg.getPlayerCompID1(); + this.playerCompID2 = msg.getPlayerCompID2(); + this.type = msg.getType(); + this.objectUUID = msg.getUUID(); + this.unknown1 = msg.getUnknown1(); + this.unknown2 = msg.getUnknown2(); + this.numItems = msg.getNumItems(); + this.unknown4 = msg.getUnknown4(); + } + + /** + * This is the general purpose constructor + */ + public TransferItemFromBankToInventoryMsg() { + super(Protocol.TRANSFERITEMFROMBANK); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferItemFromBankToInventoryMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.TRANSFERITEMFROMBANK, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + playerCompID1 = reader.getLong(); + playerCompID2 = reader.getLong(); + type = reader.getInt(); + objectUUID = reader.getInt(); + unknown1 = reader.getInt(); + unknown2 = reader.getInt(); + numItems = reader.getInt(); + unknown4 = reader.get(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putLong(playerCompID1); + writer.putLong(playerCompID2); + writer.putInt(type); + writer.putInt(objectUUID); + writer.putInt(unknown1); + writer.putInt(unknown2); + writer.putInt(numItems); + writer.put(unknown4); + } + + /** + * @return the playerCompID1 + */ + public long getPlayerCompID1() { + return playerCompID1; + } + + /** + * @param playerCompID1 the playerCompID1 to set + */ + public void setPlayerCompID1(long playerCompID1) { + this.playerCompID1 = playerCompID1; + } + + /** + * @return the playerCompID2 + */ + public long getPlayerCompID2() { + return playerCompID2; + } + + /** + * @param playerCompID2 the playerCompID2 to set + */ + public void setPlayerCompID2(long playerCompID2) { + this.playerCompID2 = playerCompID2; + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(int type) { + this.type = type; + } + + /** + * @return the objectUUID + */ + public int getUUID() { + return objectUUID; + } + + /** + * @return the unknown1 + */ + public int getUnknown1() { + return unknown1; + } + + /** + * @param unknown1 the unknown1 to set + */ + public void setUnknown1(int unknown1) { + this.unknown1 = unknown1; + } + + /** + * @return the unknown2 + */ + public int getUnknown2() { + return unknown2; + } + + /** + * @param unknown2 the unknown2 to set + */ + public void setUnknown2(int unknown2) { + this.unknown2 = unknown2; + } + + /** + * @return the numItems + */ + public int getNumItems() { + return numItems; + } + + /** + * @param numItems the numItems to set + */ + public void setNumItems(int numItems) { + this.numItems = numItems; + } + + /** + * @return the unknown4 + */ + public byte getUnknown4() { + return unknown4; + } + + /** + * @param unknown4 the unknown4 to set + */ + public void setUnknown4(byte unknown4) { + this.unknown4 = unknown4; + } + +} diff --git a/src/engine/net/client/msg/TransferItemFromEquipToInventoryMsg.java b/src/engine/net/client/msg/TransferItemFromEquipToInventoryMsg.java new file mode 100644 index 00000000..2dd59b54 --- /dev/null +++ b/src/engine/net/client/msg/TransferItemFromEquipToInventoryMsg.java @@ -0,0 +1,90 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; + +public class TransferItemFromEquipToInventoryMsg extends ClientNetMsg { + + private int playerType; + private int playerUUID; + private int slotNumber; + + /** + * This is the general purpose constructor. + */ + public TransferItemFromEquipToInventoryMsg(AbstractGameObject ago, int slotNumber) { + super(Protocol.UNEQUIP); + this.playerType = ago.getObjectType().ordinal(); + this.playerUUID = ago.getObjectUUID(); + this.slotNumber = slotNumber; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferItemFromEquipToInventoryMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UNEQUIP, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.playerType = reader.getInt(); + this.playerUUID = reader.getInt(); + this.slotNumber = reader.getInt(); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.playerType); + writer.putInt(this.playerUUID); + writer.putInt(this.slotNumber); + } + + + /** + * @return the slotNumber + */ + public int getSlotNumber() { + return slotNumber; + } + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerUUID() { + return playerUUID; + } + + public void setPlayerUUID(int playerUUID) { + this.playerUUID = playerUUID; + } + +} diff --git a/src/engine/net/client/msg/TransferItemFromInventoryToBankMsg.java b/src/engine/net/client/msg/TransferItemFromInventoryToBankMsg.java new file mode 100644 index 00000000..f4dbbd31 --- /dev/null +++ b/src/engine/net/client/msg/TransferItemFromInventoryToBankMsg.java @@ -0,0 +1,219 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +/** + * Transfer item from inventory to bank + * + * @author Eighty + */ + +public class TransferItemFromInventoryToBankMsg extends ClientNetMsg { + + private long playerCompID1; + private long playerCompID2; + private int type; + private int objectUUID; + private int unknown1; + private int unknown2; + private int numItems; + private byte unknown4; + + /** + * This is the general purpose constructor + */ + public TransferItemFromInventoryToBankMsg(long playerCompID1, + long playerCompID2, int type, int objectUUID, int unknown1, + int unknown2, int numItems, byte unknown4) { + super(Protocol.TRANSFERITEMTOBANK); + this.playerCompID1 = playerCompID1; + this.playerCompID2 = playerCompID2; + this.type = type; + this.objectUUID = objectUUID; + this.unknown1 = unknown1; + this.unknown2 = unknown2; + this.numItems = numItems; + this.unknown4 = unknown4; + } + + public TransferItemFromInventoryToBankMsg(TransferItemFromBankToInventoryMsg msg) { + super(Protocol.TRANSFERITEMTOBANK); + this.playerCompID1 = msg.getPlayerCompID1(); + this.playerCompID2 = msg.getPlayerCompID2(); + this.type = msg.getType(); + this.objectUUID = msg.getUUID(); + this.unknown1 = msg.getUnknown1(); + this.unknown2 = msg.getUnknown2(); + this.numItems = msg.getNumItems(); + this.unknown4 = msg.getUnknown4(); + } + + /** + * This is the general purpose constructor + */ + public TransferItemFromInventoryToBankMsg() { + super(Protocol.TRANSFERITEMTOBANK); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferItemFromInventoryToBankMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.TRANSFERITEMTOBANK, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + playerCompID1 = reader.getLong(); + playerCompID2 = reader.getLong(); + type = reader.getInt(); + objectUUID = reader.getInt(); + unknown1 = reader.getInt(); + unknown2 = reader.getInt(); + numItems = reader.getInt(); + unknown4 = reader.get(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putLong(playerCompID1); + writer.putLong(playerCompID2); + writer.putInt(type); + writer.putInt(objectUUID); + writer.putInt(unknown1); + writer.putInt(unknown2); + writer.putInt(numItems); + writer.put(unknown4); + } + + /** + * @return the playerCompID1 + */ + public long getPlayerCompID1() { + return playerCompID1; + } + + /** + * @param playerCompID1 the playerCompID1 to set + */ + public void setPlayerCompID1(long playerCompID1) { + this.playerCompID1 = playerCompID1; + } + + /** + * @return the playerCompID2 + */ + public long getPlayerCompID2() { + return playerCompID2; + } + + /** + * @param playerCompID2 the playerCompID2 to set + */ + public void setPlayerCompID2(long playerCompID2) { + this.playerCompID2 = playerCompID2; + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(int type) { + this.type = type; + } + + /** + * @return the objectUUID + */ + public int getUUID() { + return objectUUID; + } + + /** + * @return the unknown1 + */ + public int getUnknown1() { + return unknown1; + } + + /** + * @param unknown1 the unknown1 to set + */ + public void setUnknown1(int unknown1) { + this.unknown1 = unknown1; + } + + /** + * @return the unknown2 + */ + public int getUnknown2() { + return unknown2; + } + + /** + * @param unknown2 the unknown2 to set + */ + public void setUnknown2(int unknown2) { + this.unknown2 = unknown2; + } + + /** + * @return the numItems + */ + public int getNumItems() { + return numItems; + } + + /** + * @param numItems the numItems to set + */ + public void setNumItems(int numItems) { + this.numItems = numItems; + } + + /** + * @return the unknown4 + */ + public byte getUnknown4() { + return unknown4; + } + + /** + * @param unknown4 the unknown4 to set + */ + public void setUnknown4(byte unknown4) { + this.unknown4 = unknown4; + } + +} diff --git a/src/engine/net/client/msg/TransferItemFromInventoryToEquipMsg.java b/src/engine/net/client/msg/TransferItemFromInventoryToEquipMsg.java new file mode 100644 index 00000000..c74c035e --- /dev/null +++ b/src/engine/net/client/msg/TransferItemFromInventoryToEquipMsg.java @@ -0,0 +1,213 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; + +public class TransferItemFromInventoryToEquipMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private int pad1; + private int itemBase; + private int type; + private int objectUUID; + private int slotNumber; + private int pad2; + private float unknown1, unknown2; + + /** + * This is the general purpose constructor. + */ + public TransferItemFromInventoryToEquipMsg() { + super(Protocol.EQUIP); + } + + public TransferItemFromInventoryToEquipMsg(AbstractCharacter source, int slot, int itemBaseID) { + super(Protocol.EQUIP); + this.sourceType = source.getObjectType().ordinal(); + this.sourceID = source.getObjectUUID(); + this.slotNumber = slot; + this.itemBase = itemBaseID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferItemFromInventoryToEquipMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.EQUIP, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + pad1 = reader.getInt(); + itemBase = reader.getInt(); + type = reader.getInt(); + objectUUID = reader.getInt(); + slotNumber = reader.getInt(); + pad2 = reader.getInt(); + unknown1 = reader.getFloat(); + unknown2 = reader.getFloat(); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(pad1); + writer.putInt(itemBase); + writer.putInt(type); + writer.putInt(objectUUID); + writer.putInt(slotNumber); + writer.putInt(pad2); + writer.putFloat(unknown1); + writer.putFloat(unknown1); + } + + + /** + * @return the pad1 + */ + public int getPad1() { + return pad1; + } + + /** + * @param pad1 the pad1 to set + */ + public void setPad1(int pad1) { + this.pad1 = pad1; + } + + /** + * @return the itemBase + */ + public int getItemBase() { + return itemBase; + } + + /** + * @param itemBase the itemBase to set + */ + public void setItemBase(int itemBase) { + this.itemBase = itemBase; + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(int type) { + this.type = type; + } + + /** + * @return the objectUUID + */ + public int getUUID() { + return objectUUID; + } + + /** + * @return the slotNumber + */ + public int getSlotNumber() { + return slotNumber; + } + + /** + * @param slotNumber the slotNumber to set + */ + public void setSlotNumber(int slotNumber) { + this.slotNumber = slotNumber; + } + + /** + * @return the pad2 + */ + public int getPad2() { + return pad2; + } + + /** + * @param pad2 the pad2 to set + */ + public void setPad2(int pad2) { + this.pad2 = pad2; + } + + /** + * @return the unknown1 + */ + public float getUnknown1() { + return unknown1; + } + + /** + * @param unknown1 the unknown1 to set + */ + public void setUnknown1(float unknown1) { + this.unknown1 = unknown1; + } + + /** + * @return the unknown2 + */ + public float getUnknown2() { + return unknown2; + } + + /** + * @param unknown2 the unknown2 to set + */ + public void setUnknown2(float unknown2) { + this.unknown2 = unknown2; + } + + public int getSourceType() { + return sourceType; + } + + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + +} diff --git a/src/engine/net/client/msg/TransferItemFromInventoryToVaultMsg.java b/src/engine/net/client/msg/TransferItemFromInventoryToVaultMsg.java new file mode 100644 index 00000000..9ec0cd7a --- /dev/null +++ b/src/engine/net/client/msg/TransferItemFromInventoryToVaultMsg.java @@ -0,0 +1,200 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +/** + * Transfer item from inventory to vault + * + * @author Eighty + */ + +public class TransferItemFromInventoryToVaultMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private long playerCompID; + private int type; + private int objectUUID; + private int unknown03; + private int unknown04; + + /** + * This is the general purpose constructor + */ + public TransferItemFromInventoryToVaultMsg(long playerCompID, + int unknown01, int unknown02, int type, int objectUUID, int unknown03, + int unknown04) { + super(Protocol.ITEMTOVAULT); + this.playerCompID = playerCompID; + this.unknown01 = unknown01; + this.unknown02 = unknown02; + this.type = type; + this.objectUUID = objectUUID; + this.unknown03 = unknown03; + this.unknown04 = unknown04; + } + + public TransferItemFromInventoryToVaultMsg(TransferItemFromVaultToInventoryMsg msg) { + super(Protocol.ITEMTOVAULT); + this.playerCompID = msg.getPlayerCompID(); + this.unknown01 = msg.getUnknown01(); + this.unknown02 = msg.getUnknown02(); + this.type = msg.getType(); + this.objectUUID = msg.getUUID(); + this.unknown03 = msg.getUnknown03(); + this.unknown04 = msg.getUnknown04(); + } + + /** + * This is the general purpose constructor + */ + public TransferItemFromInventoryToVaultMsg() { + super(Protocol.ITEMTOVAULT); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferItemFromInventoryToVaultMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.ITEMTOVAULT, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + playerCompID = reader.getLong(); + unknown01 = reader.getInt(); + unknown02 = reader.getInt(); + type = reader.getInt(); + objectUUID = reader.getInt(); + unknown03 = reader.getInt(); + unknown04 = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putLong(playerCompID); + writer.putInt(unknown01); + writer.putInt(unknown02); + writer.putInt(type); + writer.putInt(objectUUID); + writer.putInt(unknown03); + writer.putInt(unknown04); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the playerCompID + */ + public long getPlayerCompID() { + return playerCompID; + } + + /** + * @param playerCompID the playerCompID to set + */ + public void setPlayerCompID(long playerCompID) { + this.playerCompID = playerCompID; + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(int type) { + this.type = type; + } + + /** + * @return the objectUUID + */ + public int getUUID() { + return objectUUID; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + +} diff --git a/src/engine/net/client/msg/TransferItemFromVaultToInventoryMsg.java b/src/engine/net/client/msg/TransferItemFromVaultToInventoryMsg.java new file mode 100644 index 00000000..053a40d2 --- /dev/null +++ b/src/engine/net/client/msg/TransferItemFromVaultToInventoryMsg.java @@ -0,0 +1,199 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +/** + * Transfer item from Vault to inventory + * + * @author Eighty + */ + +public class TransferItemFromVaultToInventoryMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private long playerCompID; + private int type; + private int objectUUID; + private int unknown03; + private int unknown04; + + /** + * This is the general purpose constructor + */ + public TransferItemFromVaultToInventoryMsg(int unknown01, int unknown02, + long playerCompID, int type, int objectUUID, int unknown03, int unknown04) { + super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY); + this.unknown01 = unknown01; + this.unknown02 = unknown02; + this.playerCompID = playerCompID; + this.type = type; + this.objectUUID = objectUUID; + this.unknown03 = unknown03; + this.unknown04 = unknown04; + } + + public TransferItemFromVaultToInventoryMsg(TransferItemFromInventoryToVaultMsg msg) { + super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY); + this.playerCompID = msg.getPlayerCompID(); + this.unknown01 = msg.getUnknown01(); + this.unknown02 = msg.getUnknown02(); + this.type = msg.getType(); + this.objectUUID = msg.getUUID(); + this.unknown03 = msg.getUnknown03(); + this.unknown04 = msg.getUnknown04(); + } + + /** + * This is the general purpose constructor + */ + public TransferItemFromVaultToInventoryMsg() { + super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public TransferItemFromVaultToInventoryMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + unknown02 = reader.getInt(); + playerCompID = reader.getLong(); + type = reader.getInt(); + objectUUID = reader.getInt(); + unknown03 = reader.getInt(); + unknown04 = reader.getInt(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putInt(unknown02); + writer.putLong(playerCompID); + writer.putInt(type); + writer.putInt(objectUUID); + writer.putInt(unknown03); + writer.putInt(unknown04); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the playerCompID + */ + public long getPlayerCompID() { + return playerCompID; + } + + /** + * @param playerCompID the playerCompID to set + */ + public void setPlayerCompID(long playerCompID) { + this.playerCompID = playerCompID; + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(int type) { + this.type = type; + } + + /** + * @return the objectUUID + */ + public int getUUID() { + return objectUUID; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + +} diff --git a/src/engine/net/client/msg/UncommitToTradeMsg.java b/src/engine/net/client/msg/UncommitToTradeMsg.java new file mode 100644 index 00000000..7a175350 --- /dev/null +++ b/src/engine/net/client/msg/UncommitToTradeMsg.java @@ -0,0 +1,93 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +/** + * Uncommit to trade + * + * @author Eighty + */ +public class UncommitToTradeMsg extends ClientNetMsg { + + private int unknown01; + private long playerCompID; + + /** + * This is the general purpose constructor + */ + public UncommitToTradeMsg(int unknown01, long playerCompID) { + super(Protocol.TRADEUNCONFIRM); + this.unknown01 = unknown01; + this.playerCompID = playerCompID; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public UncommitToTradeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.TRADEUNCONFIRM, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + unknown01 = reader.getInt(); + playerCompID = reader.getLong(); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(unknown01); + writer.putLong(playerCompID); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the playerCompID + */ + public long getPlayerCompID() { + return playerCompID; + } + + /** + * @param playerCompID the playerCompID to set + */ + public void setPlayerCompID(long playerCompID) { + this.playerCompID = playerCompID; + } + +} diff --git a/src/engine/net/client/msg/Unknown1Msg.java b/src/engine/net/client/msg/Unknown1Msg.java new file mode 100644 index 00000000..4c92abf1 --- /dev/null +++ b/src/engine/net/client/msg/Unknown1Msg.java @@ -0,0 +1,79 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class Unknown1Msg extends ClientNetMsg { + + private int targetType; + private int targetID; + + /** + * This is the general purpose constructor. + */ + public Unknown1Msg(int targetType, int targetID) { + super(Protocol.UNKNOWN1); + this.targetType = targetType; + this.targetID = targetID; + } + + /** + * This is the general purpose constructor. + */ + public Unknown1Msg() { + super(Protocol.UNKNOWN1); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public Unknown1Msg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UNKNOWN1, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + +} diff --git a/src/engine/net/client/msg/UnknownMsg.java b/src/engine/net/client/msg/UnknownMsg.java new file mode 100644 index 00000000..7d57ea49 --- /dev/null +++ b/src/engine/net/client/msg/UnknownMsg.java @@ -0,0 +1,150 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class UnknownMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int unknown03; + private short unknown04; + private byte unknown05; + + /** + * This is the general purpose constructor. + */ + public UnknownMsg() { + super(Protocol.UNKNOWN); + this.unknown01 = 0x40A5BDB0; + this.unknown02 = 0x342AA9F0; + this.unknown03 = 0; + this.unknown04 = (short) 0; + this.unknown05 = (byte) 0; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UnknownMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UNKNOWN, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putShort(this.unknown04); + writer.put(this.unknown05); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getFloat(); + reader.getString(); + reader.getString(); + reader.getInt(); + reader.get(); + reader.getInt(); + reader.getInt(); + int unknownSize = reader.getInt(); + for (int i = 0; i < unknownSize; i++) + reader.getInt(); + reader.getInt(); + reader.getString(); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public short getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(short unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public byte getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(byte unknown05) { + this.unknown05 = unknown05; + } + +} diff --git a/src/engine/net/client/msg/UnloadObjectsMsg.java b/src/engine/net/client/msg/UnloadObjectsMsg.java new file mode 100644 index 00000000..e300e646 --- /dev/null +++ b/src/engine/net/client/msg/UnloadObjectsMsg.java @@ -0,0 +1,96 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; +import org.pmw.tinylog.Logger; + +import java.util.HashMap; + + +public class UnloadObjectsMsg extends ClientNetMsg { + + private HashMap objectList = new HashMap<>(); + + /** + * This is the general purpose constructor. + */ + public UnloadObjectsMsg() { + super(Protocol.FORGETOBJECTS); + init(); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UnloadObjectsMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.FORGETOBJECTS, origin, reader); + } + + private void init() { + objectList = new HashMap<>(); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + if (this.objectList == null) { + writer.putInt(0); + return; + } + + writer.putInt(this.objectList.size()); + for (int objectUUID : this.objectList.keySet()) { + writer.putInt(this.objectList.get(objectUUID)); + writer.putInt(objectUUID); + } + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + init(); + int size = reader.getInt(); +// for (int i = 0; i < size; i++) +// this.objectList.add(reader.getLong()); + Logger.info("Client telling server to unload objects.. ??"); + } + + public HashMap getObjectList() { + return this.objectList; + } + + public void addObject(AbstractGameObject value) { + this.objectList.put(value.getObjectUUID(), value.getObjectType().ordinal()); + } + + public int size() { + return this.objectList.size(); + } + + @Override + protected int getPowerOfTwoBufferSize() { + return 13; + } +} diff --git a/src/engine/net/client/msg/UpdateCharOrMobMessage.java b/src/engine/net/client/msg/UpdateCharOrMobMessage.java new file mode 100644 index 00000000..5eae48ed --- /dev/null +++ b/src/engine/net/client/msg/UpdateCharOrMobMessage.java @@ -0,0 +1,193 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; + +public class UpdateCharOrMobMessage extends ClientNetMsg { + + private int type; + private int npcType; + private int npcID; + + private int playerType; + private int playerID; + private int size; + private int subRace; + + private int pad = 0; + private int objectType; + private int objectUUID; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateCharOrMobMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATECHARORMOB, origin, reader); + } + + public UpdateCharOrMobMessage(AbstractCharacter tar, int type, int subRaceID) { + super(Protocol.UPDATECHARORMOB); + this.playerType = tar.getObjectType().ordinal(); + this.playerID = tar.getObjectUUID(); + this.type = type; + this.subRace = subRaceID; + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.type); + if (this.type == 2) { + writer.putInt(this.playerType); + writer.putInt(this.playerID); + writer.putInt(this.subRace); + writer.putInt(-600065291); + return; + } + writer.putInt(this.playerType); + writer.putInt(this.playerID); + writer.put((byte) 1); + writer.putInt(0); + writer.putInt(this.subRace); + writer.putInt(this.playerType); + writer.putInt(this.playerID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putFloat(1); + writer.putFloat(1); + writer.putFloat(1); + writer.putFloat(1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putLong(-1); + writer.putInt(0); + writer.put((byte) 1); + writer.putInt(0); + writer.put((byte) 1); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(4); + writer.putInt(0); + writer.putInt(0); + writer.putInt(1); + writer.putShort((short) 0); + writer.put((byte) 0); + + + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int value) { + this.objectType = value; + } + + public int getUUID() { + return objectUUID; + + } + + public int getPad() { + return pad; + } + + public void setPad(int value) { + this.pad = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public int getSubRace() { + return subRace; + } + + public void setSubRace(int subRace) { + this.subRace = subRace; + } + + public int getNpcType() { + return npcType; + } + + public void setNpcType(int npcType) { + this.npcType = npcType; + } + + public int getNpcID() { + return npcID; + } + + public void setNpcID(int npcID) { + this.npcID = npcID; + } + + public int getPlayerType() { + return playerType; + } + + public void setPlayerType(int playerType) { + this.playerType = playerType; + } + + public int getPlayerID() { + return playerID; + } + + public void setPlayerID(int playerID) { + this.playerID = playerID; + } +} diff --git a/src/engine/net/client/msg/UpdateClientAlliancesMsg.java b/src/engine/net/client/msg/UpdateClientAlliancesMsg.java new file mode 100644 index 00000000..dfd0d19f --- /dev/null +++ b/src/engine/net/client/msg/UpdateClientAlliancesMsg.java @@ -0,0 +1,95 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Guild; + + +public class UpdateClientAlliancesMsg extends ClientNetMsg { + + + private int guildID; + + + public UpdateClientAlliancesMsg(Guild guild) { + super(Protocol.UPDATECLIENTALLIANCES); + this.guildID = guild.getObjectUUID(); + + } + + public UpdateClientAlliancesMsg() { + super(Protocol.UPDATECLIENTALLIANCES); + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateClientAlliancesMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATECLIENTALLIANCES, origin, reader); + } + //CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD. + + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + } + + + // Precache and configure this message before we serialize it + + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + Guild guild = Guild.getGuild(this.guildID); + + writer.putInt(guild.getAllyList().size()); + + for (Guild allies : guild.getAllyList()) { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(allies.getObjectUUID()); + } + + writer.putInt(guild.getEnemyList().size()); + for (Guild enemies : guild.getEnemyList()) { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(enemies.getObjectUUID()); + } + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); + } + + public int getGuildID() { + return guildID; + } + +} diff --git a/src/engine/net/client/msg/UpdateEffectsMsg.java b/src/engine/net/client/msg/UpdateEffectsMsg.java new file mode 100644 index 00000000..aa7a1f8b --- /dev/null +++ b/src/engine/net/client/msg/UpdateEffectsMsg.java @@ -0,0 +1,86 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; +import engine.objects.Effect; + +import java.util.ArrayList; + +public class UpdateEffectsMsg extends ClientNetMsg { + + AbstractWorldObject awo; + + /** + * This is the general purpose constructor. + */ + public UpdateEffectsMsg() { + super(Protocol.UPDATEEFFECTS); + this.awo = null; + } + + /** + * This is the general purpose constructor. + */ + public UpdateEffectsMsg(AbstractWorldObject awo) { + super(Protocol.UPDATEEFFECTS); + this.awo = awo; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateEffectsMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATEEFFECTS, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + if (awo == null) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } else { + writer.putInt(awo.getObjectType().ordinal()); + writer.putInt(awo.getObjectUUID()); + + ArrayList effects = new ArrayList<>(awo.getEffects().values()); + writer.putInt(effects.size()); + for (Effect effect : effects) + effect.serializeForClientMsg(writer); + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + public AbstractWorldObject getAwo() { + return this.awo; + } + + public void setAwo(AbstractWorldObject awo) { + this.awo = awo; + } +} diff --git a/src/engine/net/client/msg/UpdateFriendStatusMessage.java b/src/engine/net/client/msg/UpdateFriendStatusMessage.java new file mode 100644 index 00000000..2da0f7c4 --- /dev/null +++ b/src/engine/net/client/msg/UpdateFriendStatusMessage.java @@ -0,0 +1,76 @@ +/* +HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.net.client.msg; + + +import engine.Enum.GameObjectType; +import engine.gameManager.SessionManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + + +public class UpdateFriendStatusMessage extends ClientNetMsg { + + public int statusType; + public PlayerCharacter player; + public boolean online = true; + + + /** + * This is the general purpose constructor. + */ + public UpdateFriendStatusMessage(PlayerCharacter player) { + super(Protocol.UPDATEFRIENDSTATUS); + this.player = player; + this.online = SessionManager.getPlayerCharacterByID(player.getObjectUUID()) != null ? true : false; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateFriendStatusMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATEFRIENDSTATUS, origin, reader); + } + + /** + * Copy constructor + */ + public UpdateFriendStatusMessage(UpdateFriendStatusMessage msg) { + super(Protocol.UPDATEFRIENDSTATUS); + } + + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + //message is serialize only, no need for deserialize. + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + this.statusType = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(player.getObjectUUID()); + writer.putString(player.getCombinedName()); + writer.putInt(online ? 0 : 1); + writer.putInt(player.friendStatus.ordinal()); + } +} diff --git a/src/engine/net/client/msg/UpdateGoldMsg.java b/src/engine/net/client/msg/UpdateGoldMsg.java new file mode 100644 index 00000000..08feaf8a --- /dev/null +++ b/src/engine/net/client/msg/UpdateGoldMsg.java @@ -0,0 +1,118 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; +import engine.objects.CharacterItemManager; +import engine.objects.Item; +import engine.objects.PlayerCharacter; + +/** + * Update gold in inventory and/or bank + * + * @author Eighty + */ + +public class UpdateGoldMsg extends ClientNetMsg { + + CharacterItemManager itemManager; + private AbstractWorldObject looter; + private Item goldInventory; + private Item goldBank; + private int tradeGold = 0; + + + /** + * This is the general purpose constructor + */ + public UpdateGoldMsg(AbstractWorldObject player) { + super(Protocol.UPDATEGOLDVALUE); + this.looter = player; + } + + + /** + * This is the general purpose constructor + */ + public UpdateGoldMsg() { + super(Protocol.UPDATEGOLDVALUE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateGoldMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.UPDATEGOLDVALUE, origin, reader); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + // Pre-cache and set values so they are available when we + // serialize the data. + + public void configure() { + + if (this.looter != null && this.looter.getObjectType() == GameObjectType.PlayerCharacter) { + itemManager = ((PlayerCharacter) looter).getCharItemManager(); + goldInventory = itemManager.getGoldInventory(); + this.tradeGold = itemManager.getGoldTrading(); + goldBank = itemManager.getGoldBank(); + } else { + itemManager = null; + goldInventory = null; + goldBank = null; + } + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + + if (looter == null) { + writer.putInt(0); + writer.putInt(0); + } else { + writer.putInt(looter.getObjectType().ordinal()); + writer.putInt(looter.getObjectUUID()); + } + + if (goldInventory != null && goldInventory.getNumOfItems() - this.tradeGold > 0) { + writer.put((byte) 1); + Item.serializeForClientMsgWithoutSlot(goldInventory, writer); + } else + writer.put((byte) 0); + + if (goldBank != null && goldBank.getNumOfItems() != 0) { + writer.put((byte) 1); + Item.serializeForClientMsgWithoutSlot(goldBank, writer); + } else + writer.put((byte) 0); + } + +} diff --git a/src/engine/net/client/msg/UpdateInventoryMsg.java b/src/engine/net/client/msg/UpdateInventoryMsg.java new file mode 100644 index 00000000..5479f14d --- /dev/null +++ b/src/engine/net/client/msg/UpdateInventoryMsg.java @@ -0,0 +1,106 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.ItemType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Item; + +import java.util.ArrayList; + +public class UpdateInventoryMsg extends ClientNetMsg { + + private ArrayList toAdd; + private ArrayList bank; + + /** + * This is the general purpose constructor. + */ + public UpdateInventoryMsg(ArrayList inventory, ArrayList bank, Item gold, boolean add) { + super(Protocol.UPDATECLIENTINVENTORIES); + toAdd = inventory; + if (gold != null) + toAdd.add(gold); + + this.bank = bank; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateInventoryMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATECLIENTINVENTORIES, origin, reader); + } + + public static void putList(ByteBufferWriter writer, ArrayList list) { + int indexPosition = writer.position(); + writer.putInt(0); + + int serialized = 0; + + + for (Item item : list) { + if (item.getItemBase().getType().equals(ItemType.GOLD)) { + if (item.getNumOfItems() == 0) + continue; + } + Item.serializeForClientMsgWithoutSlot(item, writer); + ++serialized; + } + + writer.putIntAt(serialized, indexPosition); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + putList(writer, this.toAdd); + if (bank != null) + putList(writer, this.bank); + else + writer.putInt(0); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + public ArrayList getToAdd() { + return this.toAdd; + } + + + public void setToAdd(ArrayList value) { + this.toAdd = value; + } + + + public void addToInventory(Item value) { + this.toAdd.add(value); + } + + + @Override + protected int getPowerOfTwoBufferSize() { + return 20; + } +} diff --git a/src/engine/net/client/msg/UpdateObjectMsg.java b/src/engine/net/client/msg/UpdateObjectMsg.java new file mode 100644 index 00000000..aae3ea64 --- /dev/null +++ b/src/engine/net/client/msg/UpdateObjectMsg.java @@ -0,0 +1,136 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; + +public class UpdateObjectMsg extends ClientNetMsg { + private int msgType; + private AbstractWorldObject ago; + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateObjectMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATEOBJECT, origin, reader); + } + + public UpdateObjectMsg() { + super(Protocol.UPDATEOBJECT); + } + + public UpdateObjectMsg(AbstractWorldObject ago, int type) { + super(Protocol.UPDATEOBJECT); + if (ago == null) + return; + this.msgType = type; + this.ago = ago; + + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + writer.putInt(this.msgType); + switch (this.msgType) { + case 2: + updateName(writer); + break; + case 3: + updateRank(writer); + break; + case 4: + derank(writer); + break; + case 5: + updateGuild(writer); + break; + + default: + break; + + } + } + + private void updateName(ByteBufferWriter writer) { + + writer.putInt(ago.getObjectType().ordinal()); + writer.putInt(ago.getObjectUUID()); + writer.putString(ago.getName()); + writer.putInt(0); + } + + private void updateRank(ByteBufferWriter writer) { + writer.putInt(ago.getObjectType().ordinal()); + writer.putInt(ago.getObjectUUID()); + writer.putFloat(ago.getHealthMax()); + writer.putFloat(ago.getCurrentHitpoints()); + writer.put((byte) 1); + writer.putInt(0); + + } + + private void updateGuild(ByteBufferWriter writer) { + + writer.putInt(ago.getObjectType().ordinal()); + writer.putInt(ago.getObjectUUID()); + + switch (ago.getObjectType()) { + case Building: + Guild guild = ((Building) ago).getGuild(); + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + GuildTag._serializeForDisplay(guild.getNation().getGuildTag(), writer); + writer.putInt(0); + + break; + default: + break; + } + + + } + + private void derank(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + writer.putInt(ago.getObjectType().ordinal()); + writer.putInt(ago.getObjectUUID()); + writer.putInt(0); + } + + + public AbstractGameObject getAgo() { + return ago; + } + + +} diff --git a/src/engine/net/client/msg/UpdateStateMsg.java b/src/engine/net/client/msg/UpdateStateMsg.java new file mode 100644 index 00000000..7ac64a2c --- /dev/null +++ b/src/engine/net/client/msg/UpdateStateMsg.java @@ -0,0 +1,205 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.PlayerCharacter; + +public class UpdateStateMsg extends ClientNetMsg { + + private int charType; + private int charUUID; + private int activity; //1 dead,2 unconscious, 3 Sleeping, 4 resting,5 casting,6 IDLE,7 casting? , 8 nothing 9 unknown, + private int speed; // 1 low, 2 high (walk,run) + private int aware; // 1 low, 2 high (combat off,combat on) + + private int mode; // 0 unknown, 1 water, 2 ground, 3 flight. + private int fighting; // 1 disengaged, 2 engaged. + private int headlights; // LFGroup/LFGuild/Recruiting Icons + + /** + * This is the general purpose constructor. + */ + public UpdateStateMsg() { + super(Protocol.UPDATESTATE); + this.fighting = 1; + this.headlights = 0; + } + + public UpdateStateMsg(AbstractCharacter ac) { + super(Protocol.UPDATESTATE); + this.fighting = 1; + this.headlights = 0; + setPlayer(ac); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpdateStateMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATESTATE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.charType); + writer.putInt(this.charUUID); + writer.putInt(this.activity); + writer.putInt(this.speed); + writer.putInt(this.aware); + writer.putInt(this.mode); + writer.putInt(this.fighting); + writer.putInt(this.headlights); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.charType = reader.getInt(); + this.charUUID = reader.getInt(); + this.activity = reader.getInt(); + this.speed = reader.getInt(); + this.aware = reader.getInt(); + + this.mode = reader.getInt(); + this.fighting = reader.getInt(); + this.headlights = reader.getInt(); + } + + /** + * Sets this Msg's charUUID, sitStand, walkRun and combatToggle parameters + * based on supplied AbstractCharacter + * + * @param ac + */ + public final void setPlayer(AbstractCharacter ac) { + + PlayerCharacter player; + + this.charType = ac.getObjectType().ordinal(); + this.charUUID = ac.getObjectUUID(); + this.activity = ac.getIsSittingAsInt(); + this.speed = ac.getIsWalkingAsInt(); + this.aware = ac.getIsCombatAsInt(); + + if (ac.getObjectType() == GameObjectType.PlayerCharacter) { + player = (PlayerCharacter) ac; + this.headlights = player.getHeadlightsAsInt(); + } else + this.headlights = 0; + + this.mode = ac.getIsFlightAsInt(); + } + + /** + * @return the charUUID + */ + public int getPlayerUUID() { + return charUUID; + } + + /** + * @return the sitStand + */ + public int getActivity() { + return activity; + } + + /** + * @param activity the sitStand to set + */ + public void setActivity(int activity) { + this.activity = activity; + } + + /** + * @return the walkRun + */ + public int getSpeed() { + return speed; + } + + /** + * @param speed the walkRun to set + */ + public void setSpeed(int speed) { + this.speed = speed; + } + + /** + * @return the combatToggle + */ + public int getAware() { + return aware; + } + + /** + * @param aware the combatToggle to set + */ + public void setAware(int aware) { + this.aware = aware; + } + + /** + * @return the unknown01 + */ + public int getMode() { + return mode; + } + + /** + * @param mode the unknown01 to set + */ + public void setMode(int mode) { + this.mode = mode; + } + + /** + * @return the unknown02 + */ + public int getFighting() { + return fighting; + } + + /** + * @param fighting the unknown02 to set + */ + public void setFighting(int fighting) { + this.fighting = fighting; + } + + /** + * @return the unknown03 + */ + public int getHeadlights() { + return headlights; + } + + /** + * @param headlights the headlights to set + */ + public void setHeadlights(int headlights) { + this.headlights = headlights; + } + +} diff --git a/src/engine/net/client/msg/UpdateTradeWindowMsg.java b/src/engine/net/client/msg/UpdateTradeWindowMsg.java new file mode 100644 index 00000000..187993b4 --- /dev/null +++ b/src/engine/net/client/msg/UpdateTradeWindowMsg.java @@ -0,0 +1,95 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Item; +import engine.objects.PlayerCharacter; + +import java.util.ArrayList; + +/** + * Update trade window message. Send item info to other player. + * + * @author Eighty + */ +public class UpdateTradeWindowMsg extends ClientNetMsg { + + PlayerCharacter pc1; + PlayerCharacter pc2; + + /** + * This is the general purpose constructor. + */ + public UpdateTradeWindowMsg(PlayerCharacter pc1, PlayerCharacter pc2) { + super(Protocol.UPDATETRADEWINDOW); + this.pc1 = pc1; + this.pc2 = pc2; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public UpdateTradeWindowMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.UPDATETRADEWINDOW, origin, reader); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + + writer.putInt(pc1.getObjectType().ordinal()); + writer.putInt(pc1.getObjectUUID()); + + writer.putInt(pc2.getObjectType().ordinal()); + writer.putInt(pc2.getObjectUUID()); + + + ArrayList trading1 = new ArrayList<>(); + + for (int itemID : pc1.getCharItemManager().getTrading()) { + Item item = Item.getFromCache(itemID); + if (item == null) + continue; + trading1.add(item); + } + + ArrayList trading2 = new ArrayList<>(); + for (int itemID : pc2.getCharItemManager().getTrading()) { + Item item = Item.getFromCache(itemID); + if (item == null) + continue; + trading2.add(item); + } + Item.putTradingList(pc1, writer, trading1, false, pc1.getObjectUUID(), false, null); + Item.putTradingList(pc2, writer, trading2, false, pc2.getObjectUUID(), false, null); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + @Override + protected int getPowerOfTwoBufferSize() { + return 16; + } +} diff --git a/src/engine/net/client/msg/UpdateVaultMsg.java b/src/engine/net/client/msg/UpdateVaultMsg.java new file mode 100644 index 00000000..0ef84124 --- /dev/null +++ b/src/engine/net/client/msg/UpdateVaultMsg.java @@ -0,0 +1,69 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Account; + +/** + * Vault inventory contents + * + * @author Eighty + */ +public class UpdateVaultMsg extends ClientNetMsg { + + private int accountType; + private int accountID; + + + /** + * This is the general purpose constructor. + */ + public UpdateVaultMsg(Account account) { + super(Protocol.CLIENTUPDATEVAULT); + this.accountType = account.getObjectType().ordinal(); + this.accountID = account.getObjectUUID(); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public UpdateVaultMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.CLIENTUPDATEVAULT, origin, reader); + } + + /** + * Serializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + writer.putInt(accountType); + writer.putInt(accountID); + } + + /** + * Deserializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + @Override + protected int getPowerOfTwoBufferSize() { + return 17; + } +} diff --git a/src/engine/net/client/msg/UpgradeAssetMessage.java b/src/engine/net/client/msg/UpgradeAssetMessage.java new file mode 100644 index 00000000..d0366928 --- /dev/null +++ b/src/engine/net/client/msg/UpgradeAssetMessage.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + + +public class UpgradeAssetMessage extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int buildingUUID; + + + /** + * This is the general purpose constructor. + */ + public UpgradeAssetMessage() { + super(Protocol.UPGRADEASSET); + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UpgradeAssetMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPGRADEASSET, origin, reader); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + //Larger size for historically larger opcodes + return (16); // 2^16 == 64k + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + reader.getInt(); // Object Type Padding + this.buildingUUID = reader.getInt(); + } + + public int getUnknown01() { + return unknown01; + } + + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + public int getUnknown02() { + return unknown02; + } + + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + public int getBuildingUUID() { + return buildingUUID; + } +} diff --git a/src/engine/net/client/msg/UseCharterMsg.java b/src/engine/net/client/msg/UseCharterMsg.java new file mode 100644 index 00000000..e31d2522 --- /dev/null +++ b/src/engine/net/client/msg/UseCharterMsg.java @@ -0,0 +1,105 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.ItemType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Item; +import engine.objects.PlayerCharacter; + + +public class UseCharterMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int unknown03; + private String type; + private int unknown04; + private int unknown05; + private int unknown06; + private boolean close = false; + private PlayerCharacter player; + private int charterUUID; + + /** + * This is the general purpose constructor. + */ + public UseCharterMsg() { + super(Protocol.ACTIVATECHARTER); + } + + public UseCharterMsg(PlayerCharacter player, boolean close) { + super(Protocol.ACTIVATECHARTER); + this.close = close; + this.player = player; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public UseCharterMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ACTIVATECHARTER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + + public void configure() { + + if (close) { + for (Item i : player.getInventory()) { + if (i.getItemBase().getType().equals(ItemType.GUILDCHARTER)) { + charterUUID = i.getObjectUUID(); + break; + } + } + } + } + + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putString(this.type); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + writer.putInt(this.unknown06); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.type = reader.getString(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.getInt(); + } + + /** + * @return the unknown01 + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } +} diff --git a/src/engine/net/client/msg/VendorDialogMsg.java b/src/engine/net/client/msg/VendorDialogMsg.java new file mode 100644 index 00000000..1ced6074 --- /dev/null +++ b/src/engine/net/client/msg/VendorDialogMsg.java @@ -0,0 +1,895 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum.DispatchChannel; +import engine.Enum.GuildHistoryType; +import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.SessionManager; +import engine.math.Vector3fImmutable; +import engine.net.*; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.*; +import engine.server.MBServerStatics; + +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; + +public class VendorDialogMsg extends ClientNetMsg { + + public static final int MSG_TYPE_VENDOR = 0; + public static final int MSG_TYPE_TRAINER = 1; + public static int cnt = 1; + private int messageType; + private String language; + private int vendorObjectType; + private int vendorObjectID; + private int unknown01; + private int unknown02; + private int unknown03; + private int unknown04; + private String dialogType = "TrainerDialog"; + private String intro = "FighterIntro"; + private String introCode = " [ FighterIntro ] "; + private String merchantCode = " [ Merchant options ] "; + private int menuType = 1; // 0: close, 1: normal, 2: train, 3: untrain + private VendorDialog vd; + + /** + * This is the general purpose constructor. + */ + public VendorDialogMsg(int messageType, int vendorObjectType, int vendorObjectID, String dialogType, String intro, int menuType) { + super(Protocol.VENDORDIALOG); + this.messageType = messageType; + this.vendorObjectType = vendorObjectType; + this.vendorObjectID = vendorObjectID; + this.dialogType = dialogType; + this.intro = intro; + this.introCode = " [ " + intro + " ] "; + this.menuType = menuType; + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public VendorDialogMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.VENDORDIALOG, origin, reader); + } + + public static void replyDialog(VendorDialogMsg msg, ClientConnection origin) throws MsgSendException { + + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin); + + if (playerCharacter == null) + return; + + if (playerCharacter.getTimeStamp("lastvendorwindow") > System.currentTimeMillis()) { + return; + } + + // Get NPC that player is talking to + NPC npc = NPC.getFromCache(msg.vendorObjectID); + int npcClassID; + + if (npc == null) + return; + + // test within talking range + + if (playerCharacter.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { + ErrorPopupMsg.sendErrorPopup(playerCharacter, 14); + return; + } + + // Restrict disc trainers to only characters who have + // tht disc applied. + + npcClassID = npc.getContract().getClassID(); + + if (npc.getContract() != null && + ApplyRuneMsg.isDiscipline(npcClassID)) { + + if (playerCharacter.getRune(npcClassID) == null) { + ErrorPopupMsg.sendErrorPopup(playerCharacter, 49); + return; + } + + } + playerCharacter.setLastNPCDialog(npc); + + VendorDialog vd = null; + Contract contract = npc.getContract(); + + if (contract == null) + vd = VendorDialog.getHostileVendorDialog(); + else if (npc.getBuilding() != null) { + if (BuildingManager.IsPlayerHostile(npc.getBuilding(), playerCharacter)) + vd = VendorDialog.getHostileVendorDialog(); + else + vd = contract.getVendorDialog(); + } else + vd = contract.getVendorDialog(); + if (vd == null) + vd = VendorDialog.getHostileVendorDialog(); + + if (msg.messageType == 1 || msg.unknown03 == vd.getObjectUUID()) { + msg.updateMessage(3, vd); + } else { + if (VendorDialogMsg.handleSpecialCase(msg, npc, playerCharacter, vd, origin)) + return; + + vd = VendorDialog.getVendorDialog(msg.unknown03); + msg.updateMessage(3, vd); + } + + Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + // protected void serializeMerchantMenu(ByteBufferWriter writer) { + // writer.putInt(2); + // writer.putInt(14); + // writer.putInt(0); + // writer.putInt(0); + // writer.put((byte) 0); + // writer.put((byte) 1); + // writer.putString(" [ Merchant options ] "); + // for (int i = 0; i < 4; i++) + // writer.putInt(0); + // writer.putInt(10); + // writer.putInt(0); + // writer.putInt(0); + // writer.put((byte) 0); + // writer.put((byte) 1); + // writer.putString("Done"); + // for (int i = 0; i < 8; i++) + // writer.putInt(0); + // } + + // protected void serializeForTrain(ByteBufferWriter writer, boolean train) { + // writer.putInt(0); + // writer.putInt(0x364AF0D0); // 0x325695C0 + // writer.putInt(this.unknown03); + // writer.putInt(1); + // writer.put((byte) 0); + // writer.putInt(this.unknown03); + // writer.put((byte) 0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(2); // 2 + // if (train) + // writer.putInt(3); // 3=buy/sell/trade, 4=untrain, 5 closes + // else + // writer.putInt(4); //refine + // writer.putInt(10); // 10 + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // } + + // protected void serializeClose(ByteBufferWriter writer) { + // writer.putInt(0); + // writer.putInt(0x364AF0D0); // 0x325695C0 + // writer.putInt(this.unknown03); + // writer.putInt(1); + // writer.put((byte) 0); + // writer.putInt(this.unknown03); + // writer.put((byte) 0); + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(2); // 2 + // writer.putInt(5); // 3=buy/sell/trade, 4=untrain, 5 closes + // writer.putInt(10); // 10 + // writer.putInt(0); + // writer.putInt(0); + // writer.putInt(0); + // } + + // Handles special case menu selections, such as promote, train, ect. + private static boolean handleSpecialCase(VendorDialogMsg msg, NPC npc, PlayerCharacter playerCharacter, VendorDialog vd, ClientConnection origin) + throws MsgSendException { + + Dispatch dispatch; + int menuID = msg.unknown03; // aka menuoptions.optionID + Vector3fImmutable loc; + + switch (menuID) { + case 0: // Close Dialog + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 180: // Promote to class + VendorDialogMsg.promote(playerCharacter, npc); + msg.updateMessage(4, 0); // <-0 closes dialog + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 1000: // TrainSkill skills and powers + msg.updateMessage(4, 2); // <-2 sends trainer screen + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 1001: // Open Bank + getBank(playerCharacter, npc, origin); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 1002: // Open Vault + getVault(playerCharacter, npc, origin); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 1003: //Refine + msg.updateMessage(4, 3); // <-3 sends refine screen + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + + // Mainland Teleports + case 100011: // teleport me to Aeldreth + City city = City.getCity(25); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 10, 75, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100012: // teleport me to SDR + city = City.getCity(24); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 10, 75, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100013: // teleport me to Erkeng Hold + city = City.getCity(26); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 10, 75, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100014: // teleport me to Khan + city = City.getCity(36); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 75, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + + case 100015: // teleport me to Maelstrom + loc = new Vector3fImmutable(105100f, 40f, -25650f); + handleTeleport(playerCharacter, loc, 10, 75, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100016: // teleport me to Oblivion + loc = new Vector3fImmutable(108921f, 167f, -51590f); + handleTeleport(playerCharacter, loc, 10, 75, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100017: // teleport me to Vander's Doom + loc = new Vector3fImmutable(42033f, 46f, -54471f); + handleTeleport(playerCharacter, loc, 10, 75, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100018: // teleport me to The Sinking Isle + loc = new Vector3fImmutable(67177f, 36f, -31940f); + handleTeleport(playerCharacter, loc, 10, 75, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + + // MainLand Repledges + case 100030: // repledge me to Aeldreth + city = City.getCity(25); + if (city != null) + handleRepledge(playerCharacter, city, 10, 55, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100031: // repledge me to Sea Dog's Rest + city = City.getCity(24); + if (city != null) + handleRepledge(playerCharacter, city, 10, 75, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100032: // repledge me to Erkeng Hold + city = City.getCity(26); + if (city != null) + handleRepledge(playerCharacter, city, 10, 55, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100033: // repledge me to Khan\'Of Srekel + city = City.getCity(36); + if (city != null) + handleRepledge(playerCharacter, city, 1, 75, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100035: // repledge me to Starkholm + city = City.getCity(27); + if (city != null) + handleRepledge(playerCharacter, city, 1, 20, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + + // Noob Isle Teleports + case 100040: // teleport me to Starkholm + city = City.getCity(27); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 20, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100041: // teleport me to All-Father's Rest + city = City.getCity(28); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 20, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100042: // teleport me to Hengest + city = City.getCity(33); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 20, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100043: // teleport me to Hrimdal + city = City.getCity(30); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 20, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100044: // teleport me to Hothor's Doom + city = City.getCity(29); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 20, true); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100045: // teleport me to Scraefahl + city = City.getCity(32); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 20, false); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + case 100046: // teleport me to Valkirch + city = City.getCity(31); + if (city != null) + handleTeleport(playerCharacter, city.getLoc(), 1, 20, true); + msg.updateMessage(4, 0); + dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + + + default: + } + return false; + } + + private static boolean finishMessage(VendorDialogMsg msg, ClientConnection origin) throws MsgSendException { + msg.updateMessage(4, 0); + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + return true; + } + + private static void handleTeleport(PlayerCharacter pc, Vector3fImmutable loc, int minLevel, int maxLevel, boolean useSquare) { + if (pc == null) + return; + + int level = pc.getLevel(); + if (level >= minLevel && level <= maxLevel) { + if (useSquare) + loc = getSquare(loc); + pc.teleport(loc); + pc.setSafeMode(); + // PowersManager.applyPower(pc, pc, new Vector3f(0f, + // 0f, 0f), -1661758934, 40, false); + } else { + if (level < minLevel) + ErrorPopupMsg.sendErrorPopup(pc, 74); + else + ErrorPopupMsg.sendErrorPopup(pc, 139); + + } + } + + private static void handleRepledge(PlayerCharacter pc, City city, int minLevel, int maxLevel, boolean useSquare) { + if (pc == null || city == null) + return; + + Vector3fImmutable loc = city.getLoc(); + int level = pc.getLevel(); + if (level >= minLevel && level <= maxLevel) { + // set guild + Guild guild = city.getGuild(); + if (guild != null) { + // teleport player + if (useSquare) + loc = getSquare(loc); + pc.teleport(loc); + pc.setSafeMode(); + // PowersManager.applyPower(pc, pc, new + // Vector3f(0f, 0f, 0f), -1661758934, 40, false); + + // join guild + GuildManager.joinGuild(pc, guild, GuildHistoryType.JOIN); + + pc.resetGuildStatuses(); + + if (guild.isNPCGuild()) + pc.setFullMember(true); + + if (useSquare) + loc = loc.add(30, 0, 0); + pc.setBindLoc(loc); + } else { + // guild not found, just teleport + if (useSquare) + loc = getSquare(loc); + pc.teleport(loc); + pc.setSafeMode(); + // PowersManager.applyPower(pc, pc, new + // Vector3f(0f, 0f, 0f), -1661758934, 50, false); + } + } else { + + if (level < minLevel) + ErrorPopupMsg.sendErrorPopup(pc, 74); + else + ErrorPopupMsg.sendErrorPopup(pc, 139); + } + } + + // randomly place around a tol using a square (+- 30 units in x and z + // direction) + public static Vector3fImmutable getSquare(Vector3fImmutable cityLoc) { + Vector3fImmutable loc = cityLoc; + // get direction + int roll = ThreadLocalRandom.current().nextInt(4); + if (roll == 0) { // north + loc = loc.add((ThreadLocalRandom.current().nextInt(60) - 30), 0, -30); + } else if (roll == 1) { // south + loc = loc.add((ThreadLocalRandom.current().nextInt(60) - 30), 0, 30); + } else if (roll == 2) { // east + loc = loc.add(30, 0, (ThreadLocalRandom.current().nextInt(60) - 30)); + } else { // west + loc = loc.add(-30, 0, (ThreadLocalRandom.current().nextInt(60) - 30)); + } + + // Make sure no one gets stuck in the tree. + + if (loc.distanceSquared2D(cityLoc) < 250) { + loc = cityLoc; + loc = loc.add(30, 0, 0); + } + + return loc; + } + + // Handle promotion + private static void promote(PlayerCharacter pc, NPC npc) { + + if (npc == null || pc == null) + return; + + // test level 10 + if (pc.getLevel() < 10) { + // TODO send client promotion error + while (pc.getLevel() > 65) + pc.setLevel((short) 65); + return; + } + + // verify player not already promoted + if (pc.getPromotionClass() != null) { + // TODO send client promotion error + return; + } + + // Get promotion class for npc + Contract contract = npc.getContract(); + if (contract == null) + return; + int promoID = contract.getPromotionClass(); + if (promoID == 0) + return; + PromotionClass promo = DbManager.PromotionQueries.GET_PROMOTION_CLASS(promoID); + if (promo == null) { + // TODO log error here + return; + } + + // verify race valid for profession + Race race = pc.getRace(); + if (race == null || !promo.isAllowedRune(race.getToken())) { + // TODO send client promotion error + return; + } + + // verify baseclass valid for profession + BaseClass bc = pc.getBaseClass(); + if (bc == null || !promo.isAllowedRune(bc.getToken())) { + // TODO send client promotion error + return; + } + + // verify gender + if (promoID == 2511 && pc.isMale()) // Fury + return; + if (promoID == 2512 && pc.isMale()) // Huntress + return; + if (promoID == 2517 && !pc.isMale()) // Warlock + return; + + // Everything valid. Let's promote + pc.setPromotionClass(promo.getObjectUUID()); + + //pc.setLevel((short) 65); + + + promo = pc.getPromotionClass(); + if (promo == null) { + // TODO log error here + return; + } + + // recalculate all bonuses/formulas/skills/powers + pc.recalculate(); + + // send the rune application to the clients + ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), promo.getObjectUUID(), promo.getObjectType().ordinal(), promo + .getObjectUUID(), true); + DispatchMessage.dispatchMsgToInterestArea(pc, arm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + + } + + /** + * Load and send vault to player. This is public only so a DevCmd can hook + * into it. + * + * @param playerCharacter - Player Character requesting vault + * @param target - NPC vaultkeeper + * @param cc - Client Connection + */ + public static void getVault(PlayerCharacter playerCharacter, NPC target, ClientConnection cc) { + if (playerCharacter == null || cc == null || target == null) + return; + + Account ac = playerCharacter.getAccount(); + if (ac == null) + return; + + CharacterItemManager itemManager = playerCharacter.getCharItemManager(); + if (itemManager == null) + return; + + // TODO uncomment this block after we determine when we + // setBankOpen(false) + /* + * // cannot have bank and vault open at the same time if + * (itemManager.isBankOpen()) return; + */ + + if (itemManager.getTradingWith() != null) { + return; + // TODO close trade window here - simple once this is moved to WS + } + + itemManager.setVaultOpen(true); + + // TODO for public test - remove this afterwards + // DevCmd.fillVault(pc, itemManager); + + // TODO When do we setVaultOpen(false)? I don't think the client sends a + // "CloseVault" message. + + OpenVaultMsg openVaultMsg = new OpenVaultMsg(playerCharacter, target); + Dispatch dispatch = Dispatch.borrow(playerCharacter, openVaultMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ShowVaultInventoryMsg showVaultInventoryMsg = new ShowVaultInventoryMsg(playerCharacter, ac, target); // 37?? + dispatch = Dispatch.borrow(playerCharacter, showVaultInventoryMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + // All recordings have "open - show - open." + // Seems to work fine with just "show - open" as well. + + } + + /** + * Load and send Bank to player. This is public only so a DevCmd can hook + * into it. + * + * @param playerCharacter - Player Character requesting vault + * @param target - NPC vaultkeeper + * @param cc - Client Connection + */ + public static void getBank(PlayerCharacter playerCharacter, NPC target, ClientConnection cc) { + + if (playerCharacter == null) + return; + + if (cc == null) + return; + + CharacterItemManager itemManager = playerCharacter.getCharItemManager(); + + if (itemManager == null) + return; + + // TODO uncomment this block after we determine when we + // setVaultOpen(false) + /* + * // cannot have bank and vault open at the same time if + * (itemManager.isVaultOpen()) return; + */ + + if (itemManager.getTradingWith() != null) { + return; + // TODO close trade window here - simple once this is moved to WS + } + + itemManager.setBankOpen(true); + // TODO When do we setBankOpen(false)? I don't think the client sends a + // "CloseBank" message. + + AckBankWindowOpenedMsg ackBankWindowOpenedMsg = new AckBankWindowOpenedMsg(playerCharacter, 0L, 0L); + + Dispatch dispatch = Dispatch.borrow(playerCharacter, ackBankWindowOpenedMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ReqBankInventoryMsg reqBankInventoryMsg = new ReqBankInventoryMsg(playerCharacter, 0L); + dispatch = Dispatch.borrow(playerCharacter, reqBankInventoryMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + ShowBankInventoryMsg showBankInventoryMsg = new ShowBankInventoryMsg(playerCharacter, 0L); + dispatch = Dispatch.borrow(playerCharacter, showBankInventoryMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + writer.putInt(this.messageType); + for (int i = 0; i < 3; i++) + writer.putInt(0); + if (messageType == 1) + writer.putString(this.language); + else + writer.putString(""); + writer.putInt(this.vendorObjectType); + writer.putInt(this.vendorObjectID); + + for (int i = 0; i < 3; i++) + writer.putInt(0); + writer.put((byte) 0); + writer.put((byte) 0); + if (this.messageType == 1) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + } else if (this.messageType == 4) { + writer.putInt(0); + writer.putInt(0x364AF0D0); // 0x325695C0 + if (this.vd != null) + writer.putInt(vd.getObjectUUID()); + else + writer.putInt(this.unknown03); + writer.putInt(1); + writer.put((byte) 0); + writer.putInt(this.unknown03); + writer.put((byte) 0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(2); // 2 + if (menuType == 2) + writer.putInt(3); + else if (menuType == 3) + writer.putInt(4); + else + writer.putInt(5); + // writer.putInt(3); // 3=buy/sell/trade, 4=untrain, 5 closes + writer.putInt(10); // 10 + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + return; + + } + writer.putInt(15); + writer.putInt(5); + if (this.vd != null) + writer.putInt(vd.getObjectUUID()); + else + writer.putInt(this.unknown03); + + //filename datablock + writer.putInt(1); + writer.put((byte) 1); + writer.putInt(this.unknown03); + writer.put((byte) 1); + writer.putString(vd.getDialogType()); + writer.putInt(0); + writer.putInt(1); + writer.putInt(4); + writer.putInt(0); + writer.put((byte) 1); + + //vendor dialog datablock + writer.putString(vd.getDialogType()); + writer.putString(vd.getIntro()); + writer.put((byte) 0); + + //menu options datablock + writer.putInt(1); + writer.putString(" [ " + vd.getIntro() + " ] "); + ArrayList options = vd.getOptions(); + writer.putInt((options.size() + 1)); + for (MenuOption option : options) { + if (option.getMessage().equals(" [ Merchant options ] ")) { + writer.putInt(16); + } else { + writer.putInt(14); + } + writer.put((byte) 0); + writer.putInt(0); + writer.put((byte) 0); + writer.putInt(1); + writer.putString(option.getMessage()); + writer.putInt(option.getOptionID()); + for (int i = 0; i < 3; i++) + writer.putInt(0); + } + writer.putInt(10); + writer.put((byte) 0); + writer.putInt(0); + writer.put((byte) 0); + writer.putInt(1); + writer.putString("Done"); + for (int i = 0; i < 4; i++) + writer.putInt(0); + // writer.putInt(1); + // writer.putInt(2); + for (int i = 0; i < 4; i++) + writer.putInt(0); + } + + /** + * Deserializes the subclass specific items from the supplied + * ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.messageType = reader.getInt(); + for (int i = 0; i < 3; i++) + reader.getInt(); + this.language = reader.getString(); + this.vendorObjectType = reader.getInt(); + this.vendorObjectID = reader.getInt(); + for (int i = 0; i < 3; i++) + reader.getInt(); + reader.get(); + reader.get(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + reader.getInt(); + // if (this.messageType == 1) { + reader.get(); + reader.getInt(); + reader.get(); + reader.getInt(); + reader.getShort(); + // return; + // } + // reader.get(); + // this.unknown04 = reader.getInt(); + // reader.get(); + // TODO more message to go here + } + + public int getMessageType() { + return this.messageType; + } + + public void setMessageType(int value) { + this.messageType = value; + } + + public int getVendorObjectType() { + return this.vendorObjectType; + } + + public int getVendorObjectID() { + return this.vendorObjectID; + } + + public int getUnknown01() { + return this.unknown01; + } + + public int getUnknown02() { + return this.unknown02; + } + + public int getUnknown03() { + return this.unknown03; + } + + public void setUnknown03(int value) { + this.unknown03 = value; + } + + public void setLanguage(String value) { + this.language = value; + } + + public void updateMessage(int messageType, int menuType) { + this.messageType = messageType; + this.menuType = menuType; + } + + public void updateMessage(int messageType, String dialogType, String intro, int menuType) { + this.messageType = messageType; + this.dialogType = dialogType; + this.intro = intro; + this.introCode = " [ " + this.intro + " ] "; + this.menuType = menuType; + } + + public void updateMessage(int messageType, VendorDialog vd) { + this.messageType = messageType; + this.vd = vd; + } +} diff --git a/src/engine/net/client/msg/ViewResourcesMessage.java b/src/engine/net/client/msg/ViewResourcesMessage.java new file mode 100644 index 00000000..9457e159 --- /dev/null +++ b/src/engine/net/client/msg/ViewResourcesMessage.java @@ -0,0 +1,195 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.*; + + +public class ViewResourcesMessage extends ClientNetMsg { + + //resource hashes + //0001240F + //002339C7 (locked) + //00263669 + //00270DC3 + //002D6DEF + //047636B3 (locked) + //047B0CC1 + //04AB3761 + //1AF5DB3A + //47033237 + //4F8EFB0F + //5B57C3E4 + //86A0AC24 + //9705591E + //98378CB4 + //98D78D15 + //A0703E8C (locked) + //A0DA3807 + //A1723A93 + //A26E59CF + //D665C60F + //E3D05AE3 + //ED13904D + + private Guild guild; + private Building warehouseBuilding; + private Warehouse warehouseObject; + private PlayerCharacter player; + private City city; + + /** + * This is the general purpose constructor. + */ + + public ViewResourcesMessage(PlayerCharacter player) { + super(Protocol.VIEWRESOURCES); + this.guild = null; + this.player = player; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ViewResourcesMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.VIEWRESOURCES, origin, reader); + } + + public boolean configure() { + + if (this.warehouseBuilding.getParentZone() == null) + return false; + + this.city = (City) DbManager.getObject(Enum.GameObjectType.City, this.warehouseBuilding.getParentZone().getPlayerCityUUID()); + + if (this.city == null) + return false; + + this.warehouseObject = this.city.getWarehouse(); + + return this.warehouseObject != null; + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + writer.putInt(warehouseObject.getResources().size()); + + for (ItemBase ib : (warehouseObject.getResources().keySet())) { + + writer.putInt(ib.getHashID()); + writer.putInt((warehouseObject.getResources().get(ib))); + + + if (warehouseObject.isResourceLocked(ib) == true) + writer.put((byte) 1); + else + writer.put((byte) 0); + } + + writer.putInt(warehouseObject.getResources().size()); + + for (ItemBase ib : warehouseObject.getResources().keySet()) { + writer.putInt(ib.getHashID()); + writer.putInt(0); //available? + writer.putInt(Warehouse.getMaxResources().get(ib.getUUID())); //max? + } + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + + // Serialize what tags? Errant? + + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + + if (GuildStatusController.isTaxCollector(player.getGuildStatus())) { + writer.putInt(1); + writer.putString("Deposit"); + writer.putInt(-1760114543); + writer.putInt(1); + writer.put((byte) 0); + + } else if (this.player.getGuild().equals(warehouseBuilding.getGuild()) && (GuildStatusController.isInnerCouncil(this.player.getGuildStatus()))) { + writer.putInt(4); + writer.putString("Lock"); + writer.putInt(2393548); + writer.putInt(1); //locked? on/off + writer.put((byte) 0); + writer.putString("Deposit"); + writer.putInt(-1760114543); + + writer.putInt(1); + writer.put((byte) 0); + writer.putString("Manage Mines"); + writer.putInt(-820683698); + writer.putInt(1); + writer.put((byte) 0); + writer.putString("Withdraw"); + writer.putInt(-530228289); + writer.putInt(1); + writer.put((byte) 0); + } else { + writer.putInt(2); + writer.putString("Lock"); + writer.putInt(2393548); + writer.putInt(0); //locked? on/off + writer.put((byte) 0); + writer.putString("Deposit"); + writer.putInt(-1760114543); + writer.putInt(1); + writer.put((byte) 0); + } + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + // this.locX = reader.getFloat(); + // this.locY = reader.getFloat(); + // this.locZ = reader.getFloat(); + // this.name = reader.getString(); + // this.unknown01 = reader.getInt(); + } + + public void setGuild(Guild guild) { + this.guild = guild; + } + + public void setWarehouseBuilding(Building warehouseBuilding) { + this.warehouseBuilding = warehouseBuilding; + } +} diff --git a/src/engine/net/client/msg/VisualUpdateMessage.java b/src/engine/net/client/msg/VisualUpdateMessage.java new file mode 100644 index 00000000..5a5e8035 --- /dev/null +++ b/src/engine/net/client/msg/VisualUpdateMessage.java @@ -0,0 +1,93 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.Enum.GameObjectType; +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractGameObject; +import engine.objects.Building; + +public class VisualUpdateMessage extends ClientNetMsg { + private int effectType; + private AbstractGameObject ago; + private Building building; + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public VisualUpdateMessage(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.VISUALUPDATE, origin, reader); + } + + public VisualUpdateMessage() { + super(Protocol.VISUALUPDATE); + } + + public VisualUpdateMessage(AbstractGameObject ago, int visualID) { + super(Protocol.VISUALUPDATE); + + if (ago == null) + return; + + this.effectType = visualID; + this.ago = ago; + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + public void configure() { + + if (this.ago.getObjectType() == GameObjectType.Building) + this.building = (Building) this.ago; + else + this.building = null; + + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) throws SerializationException { + + if (this.building == null) { + writer.putInt(4); + writer.putInt(0); + writer.putInt(this.effectType); + writer.putInt(ago.getObjectType().ordinal()); + writer.putInt(ago.getObjectUUID()); + writer.putInt(0); + return; + } + + writer.putShort((short) 100); + writer.putShort((short) 120); + writer.putInt(1); + writer.putInt(this.building.getObjectType().ordinal()); + writer.putInt(this.building.getObjectUUID()); + writer.putInt(this.effectType); + + } +} diff --git a/src/engine/net/client/msg/WhoRequestMsg.java b/src/engine/net/client/msg/WhoRequestMsg.java new file mode 100644 index 00000000..93433461 --- /dev/null +++ b/src/engine/net/client/msg/WhoRequestMsg.java @@ -0,0 +1,103 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; + +public class WhoRequestMsg extends ClientNetMsg { + + private int set; + private int filterType; + private String filter; + + /** + * This is the general purpose constructor. + */ + public WhoRequestMsg() { + super(Protocol.WHOREQUEST); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public WhoRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.WHOREQUEST, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.set); + writer.putInt(this.filterType); + writer.putString(this.filter); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.set = reader.getInt(); + this.filterType = reader.getInt(); + this.filter = reader.getString(); + } + + /** + * @return the set + */ + public int getSet() { + return set; + } + + /** + * @param set the set to set + */ + public void setSet(int set) { + this.set = set; + } + + /** + * @return the filterType + */ + public int getFilterType() { + return filterType; + } + + /** + * @param filterType the filterType to set + */ + public void setFilterType(int filterType) { + this.filterType = filterType; + } + + /** + * @return the filter + */ + public String getFilter() { + return filter; + } + + /** + * @param filter the filter to set + */ + public void setFilter(String filter) { + this.filter = filter; + } + +} diff --git a/src/engine/net/client/msg/WhoResponseMsg.java b/src/engine/net/client/msg/WhoResponseMsg.java new file mode 100644 index 00000000..dc27c00b --- /dev/null +++ b/src/engine/net/client/msg/WhoResponseMsg.java @@ -0,0 +1,300 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.Enum; +import engine.gameManager.SessionManager; +import engine.net.*; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.Guild; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; +import engine.objects.PromotionClass; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +public class WhoResponseMsg extends ClientNetMsg { + + private static int worldPop; + private int unknown01; + private ArrayList members = new ArrayList<>(); + + /** + * This is the general purpose constructor. + */ + public WhoResponseMsg() { + super(Protocol.WHORESPONSE); + this.unknown01 = 1; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public WhoResponseMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.WHORESPONSE, origin, reader); + } + + /** + * @return the worldPop + */ + public static int getWorldPop() { + return WhoResponseMsg.worldPop; + } + + /** + * @param worldPop the worldPop to set + */ + public static void setWorldPop(int worldPop) { + WhoResponseMsg.worldPop = worldPop; + } + + public static void HandleResponse(int set, int filterType, String filter, ClientConnection origin) { + + WhoResponseMsg msg = new WhoResponseMsg(); + WhoResponseMsg.setWorldPop(SessionManager.getAllActivePlayerCharacters().size()); + + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin); + + if (playerCharacter != null) { + //check threshold + long currentTime = System.currentTimeMillis(); + long timestamp = playerCharacter.getTimeStamp("whoWindow"); + long dif = currentTime - timestamp; + if (dif < MBServerStatics.WHO_WINDOW_THRESHOLD) + return; + playerCharacter.setTimeStamp("whoWindow", currentTime); + } + + if (playerCharacter == null) { + } else if (filterType == 0) { // No Filter, send everyone + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) + if (player != null) + if (player.isActive()) + if (!isAdmin(player)) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + } else if (filterType == 1) { // Race Filter + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) + if (player != null) + if (!isAdmin(player)) + if (player.isActive()) { + String[] race = player.getRace().getName().split(","); + if (filter.compareTo(race[0]) == 0) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + } + } else if (filterType == 2) { // Class Filter + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) + if (player != null) + if (!isAdmin(player)) + if (player.isActive()) { + if (filter.compareTo(player.getBaseClass().getName()) == 0 || (player.getPromotionClass() != null && filter.compareTo(player.getPromotionClass().getName()) == 0)) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + + + // TODO Promotion Class needs added to + // PlayerCharacter + // else if + // (filter.compareTo(pc.getPromotionClass().getName()) + // == 0) + // if (!HandleSet(set, pc, ori, msg)) + // break; + } + } else if (filterType == 3) { // Level Filter + String range[] = filter.split(" "); + int low; + int high; + + try { + low = Integer.parseInt(range[0]); + } catch (NumberFormatException e) { + low = 1; + Logger.error("WhoResponseMsg: Low value in filter is not a proper integer. Defaulting to 1. Error = " + + e.getMessage()); + } + + try { + high = Integer.parseInt(range[1]); + } catch (NumberFormatException e) { + high = 1; + Logger.error( + "WhoResponseMsg: High value in filter is not a proper integer. Defaulting to 75. Error = " + e.getMessage()); + } + + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) + if (player != null) + if (!isAdmin(player)) + if (player.isActive()) + if (player.getLevel() >= low && player.getLevel() <= high) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + } else if (filterType == 4) { // Name Filter + filter = filter.toLowerCase(); + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) + if (player != null) + if (!isAdmin(player)) + if (player.isActive()) + if (player.getName().toLowerCase().indexOf(filter) > -1) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + } else if (filterType == 6) { // Status Filter + int type = Integer.parseInt(filter); + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) + if (player != null) + if (!isAdmin(player)) + if (player.isActive()) { + if (type == 1) { + if (player.isLFGroup()) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + } else if (type == 2) { + if (player.isLFGuild()) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + } else if (type == 3) { + if (player.isRecruiting()) + if (!HandleSet(set, player, playerCharacter, msg)) + break; + } else + break; + } + } + + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + private static boolean HandleSet(int set, PlayerCharacter a, PlayerCharacter b, WhoResponseMsg msg) { + // This function handles the sets. 0 = search all, 1 = search nation, + // 2 = search guild. Returns true until 100 (max) players found + + if (set == 0) { // All + return msg.addMember(a); + } else if (set == 1) { // Nation + if (compareNation(a, b)) + return msg.addMember(a); + } else if (set == 2) { // Guild + if (compareGuild(a, b)) + return msg.addMember(a); + } + return true; + } + + private static boolean isAdmin(PlayerCharacter pc) { + PromotionClass promo = pc.getPromotionClass(); + if (promo == null) + return false; + return promo.getObjectUUID() <= 2503 || promo.getObjectUUID() >= 2527; + } + + private static boolean compareGuild(PlayerCharacter a, PlayerCharacter b) { + if (a == null || b == null) + return false; + return Guild.sameGuild(a.getGuild(), b.getGuild()); + } + + private static boolean compareNation(PlayerCharacter a, PlayerCharacter b) { + if (a == null || b == null) + return false; + Guild aG = a.getGuild(); + Guild bG = b.getGuild(); + if (aG == null || bG == null) + return false; + return (aG.getNation() == bG.getNation()) && aG.getNation() != null; + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(unknown01); + writer.putInt(worldPop); + int size = this.members.size(); + writer.putInt(size); + //PlayerCharacter pc : this.members + for (PlayerCharacter pc : this.members) { + writer.putInt(pc.getObjectType().ordinal()); + writer.putInt(pc.getObjectUUID()); + writer.putString(pc.getFirstName()); + writer.putString(pc.getLastName()); + writer.putInt(pc.getRaceToken()); + writer.putInt(pc.getClassToken()); + writer.putInt(pc.getLevel()); + writer.putInt(0); // unknown 0 + writer.putInt(pc.isMale() ? 1 : 2); //gender? + writer.putInt(0); // unknown 0 + Guild guild = pc.getGuild(); + if (guild != null) { + writer.put((byte) 1); // Send Guild Info + writer.put((byte) 1); // SkipPartTwo + writer.putString(guild.getName()); + writer.putInt(guild.getCharter()); // Charter Type + writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus())); + writer.putString("what"); // City?, Skip if SkipPartTwo = 0x00 + } else { + writer.put((byte) 0); // Don't Send guild info + writer.put((byte) 0); // Don't send last string + } + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + WhoResponseMsg.worldPop = reader.getInt(); + // TODO implement Deserialization + } + + /** + * @return the number of PlayerCharacters + */ + public int getSize() { + return this.members.size(); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + public boolean addMember(PlayerCharacter pc) { + if (this.members.size() > 100) + return false; + this.members.add(pc); + return true; + } + + @Override + protected int getPowerOfTwoBufferSize() { + return 14; + } + +} diff --git a/src/engine/net/client/msg/WorldDataMsg.java b/src/engine/net/client/msg/WorldDataMsg.java new file mode 100644 index 00000000..2db0ee11 --- /dev/null +++ b/src/engine/net/client/msg/WorldDataMsg.java @@ -0,0 +1,130 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + + +import engine.exception.SerializationException; +import engine.gameManager.ConfigManager; +import engine.gameManager.ZoneManager; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Zone; +import engine.server.world.WorldServer; +import org.pmw.tinylog.Logger; + +public class WorldDataMsg extends ClientNetMsg { + + public static final long wdComp = 0xFF00FF0000000001L; + private static byte ver; + + /** + * This is the general purpose constructor. + */ + public WorldDataMsg() { + super(Protocol.NEWWORLD); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public WorldDataMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.NEWWORLD, origin, reader); + } + + private static int getTotalMapSize(Zone root) { + if (root.getNodes().isEmpty()) + return 0; + + int size = root.getNodes().size(); + for (Zone child : root.getNodes()) + size += getTotalMapSize(child); + return size; + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return (18); // 2^17 == 131,072 + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) + throws SerializationException { + + + // TODO replace this return with SerializationException + + Zone root = ZoneManager.getSeaFloor(); + if (root == null) { + Logger.error("Failed to find Sea Floor!"); + return; + } + + writer.putString(ConfigManager.MB_WORLD_NAME.getValue()); + writer.putInt(512); + writer.putInt(384); + + writer.putInt(WorldServer.worldMapID); + writer.putInt(0x00000000); + + writer.putInt(getTotalMapSize(root) + 1); + Zone.serializeForClientMsg(root, writer); + + Zone hotzone = ZoneManager.hotZone; + ; + + if (hotzone == null) + writer.putLong(0L); + else { + writer.putInt(hotzone.getObjectType().ordinal()); + writer.putInt(hotzone.getObjectUUID()); + } + + + writer.putFloat(0); + writer.putFloat(1); + writer.putFloat(0); + writer.putFloat(0.69999999f); + writer.putFloat(.5f); + writer.putFloat(1); + writer.putFloat(.5f); + writer.putFloat(0.69999999f); + writer.putFloat(1); + writer.putFloat(0); + writer.putFloat(0); + writer.putFloat(0.69999999f); + writer.putFloat(1); + writer.putFloat(.5f); + writer.putFloat(.5f); + writer.putFloat(0.69999999f); + writer.putFloat(1); + + + } + + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + +} diff --git a/src/engine/net/client/msg/WorldRealmMsg.java b/src/engine/net/client/msg/WorldRealmMsg.java new file mode 100644 index 00000000..fde56eb5 --- /dev/null +++ b/src/engine/net/client/msg/WorldRealmMsg.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg; + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.Realm; +import engine.server.world.WorldServer; + + +public class WorldRealmMsg extends ClientNetMsg { + + + /** + * This is the general purpose constructor. + */ + public WorldRealmMsg() { + super(Protocol.REALMDATA); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public WorldRealmMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REALMDATA, origin, reader); + } + + @Override + protected int getPowerOfTwoBufferSize() { + return (14); // 2^14 == 16384 + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + + @Override + protected void _serialize(ByteBufferWriter writer) { + + int realmCount; + int realmID; + Realm serverRealm; + + + realmCount = Realm._realms.size(); + + writer.putInt(realmCount); + + for (Realm realm : Realm._realms.values()) + realm.serializeForClientMsg(writer); + + writer.putInt(0x0); + writer.putInt(WorldServer.worldRealmMap); + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + // TODO Implement Deserialization + } +} diff --git a/src/engine/net/client/msg/chat/AbstractChatMsg.java b/src/engine/net/client/msg/chat/AbstractChatMsg.java new file mode 100644 index 00000000..5b69e03a --- /dev/null +++ b/src/engine/net/client/msg/chat/AbstractChatMsg.java @@ -0,0 +1,182 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.AbstractWorldObject; + +public abstract class AbstractChatMsg extends ClientNetMsg { + + protected int sourceType; + protected int sourceID; + protected String sourceName; + protected AbstractWorldObject source; + + protected int unknown01; + protected String message; + protected int unknown02; + + /** + * This is the general purpose constructor. + */ + protected AbstractChatMsg(Protocol protocolMsg, AbstractWorldObject source, String message) { + super(protocolMsg); + + this.unknown01 = 0; + this.message = message; + this.source = source; + this.unknown02 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + protected AbstractChatMsg(Protocol protocolMsg, AbstractConnection origin, ByteBufferReader reader) { + super(protocolMsg, origin, reader); + + //THIS may cause initialization error, but messing up guild chat + //this.unknown01 = 0; + //this.unknown02 = 0; + } + + /** + * Copy constructor + */ + protected AbstractChatMsg(AbstractChatMsg msg) { + super(msg.getProtocolMsg()); + this.sourceType = msg.sourceType; + this.sourceID = msg.sourceID; + this.sourceName = msg.sourceName; + this.source = msg.source; + this.unknown01 = msg.unknown01; + this.message = msg.getMessage(); + this.unknown02 = msg.getUnknown02(); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected abstract void _deserialize(ByteBufferReader reader); + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected abstract void _serialize(ByteBufferWriter writer) throws SerializationException; + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the sourceObjName + */ + public AbstractWorldObject getSource() { + return source; + } + + /** + * @param sourceObjName the sourceObjName to set + */ + public void setSource(AbstractWorldObject source) { + this.source = source; + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + /** + * @param sourceType the sourceType to set + */ + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + /** + * @param sourceID the sourceID to set + */ + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + + /** + * @return the sourceName + */ + public String getSourceName() { + return sourceName; + } + + /** + * @param sourceName the sourceName to set + */ + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + +} diff --git a/src/engine/net/client/msg/chat/ChatCSRMsg.java b/src/engine/net/client/msg/chat/ChatCSRMsg.java new file mode 100644 index 00000000..1aff5bbd --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatCSRMsg.java @@ -0,0 +1,78 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.gameManager.SessionManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; +import engine.server.world.WorldServer; +import engine.session.Session; + +public class ChatCSRMsg extends AbstractChatMsg { + + /** + * This is the general purpose constructor. + */ + public ChatCSRMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATCSR, source, message); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatCSRMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATCSR, origin, reader); + Session s = SessionManager.getSession((ClientConnection) origin); + if (s == null) + return; + this.source = s.getPlayerCharacter(); + } + + /** + * Copy constructor + */ + public ChatCSRMsg(ChatCSRMsg msg) { + super(msg); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getLong(); + this.message = reader.getString(); + reader.getInt(); // pad + this.unknown02 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.source.getObjectType().ordinal()); + writer.putInt(this.source.getObjectUUID()); + writer.putInt(this.unknown01); + writer.putString(this.message); + writer.putString(this.source.getName()); + writer.putInt(WorldServer.worldMapID); + } + +} diff --git a/src/engine/net/client/msg/chat/ChatCityMsg.java b/src/engine/net/client/msg/chat/ChatCityMsg.java new file mode 100644 index 00000000..b39bef3c --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatCityMsg.java @@ -0,0 +1,86 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + +import engine.Enum.GameObjectType; +import engine.gameManager.SessionManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractGameObject; +import engine.objects.AbstractWorldObject; +import engine.server.world.WorldServer; + +public class ChatCityMsg extends AbstractChatMsg { + + /** + * This is the general purpose constructor. + */ + public ChatCityMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATCITY, source, message); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatCityMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATCITY, origin, reader); + } + + /** + * Copy constructor + */ + public ChatCityMsg(ChatCityMsg msg) { + super(msg); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + long sourceID = reader.getLong(); + int objectUUID = AbstractGameObject.extractUUID(GameObjectType.PlayerCharacter, sourceID); + this.source = SessionManager.getPlayerCharacterByID(objectUUID); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + this.sourceName = reader.getString(); + this.unknown02 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + // TODO Implement Serialize + if (this.source != null) { + writer.putInt(source.getObjectType().ordinal()); + writer.putInt(source.getObjectUUID()); + } else + writer.putLong(0L); + writer.putInt(0); + writer.putString(this.message); + if (this.source == null) { + // TODO log error here + writer.putString(""); + writer.putInt(0); + } else { + writer.putString(((AbstractCharacter) this.source).getFirstName()); + writer.putInt(WorldServer.worldMapID); + } + } + +} diff --git a/src/engine/net/client/msg/chat/ChatGlobalMsg.java b/src/engine/net/client/msg/chat/ChatGlobalMsg.java new file mode 100644 index 00000000..f7658ddd --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatGlobalMsg.java @@ -0,0 +1,89 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.server.world.WorldServer; + +public class ChatGlobalMsg extends AbstractChatMsg { + + /** + * This is the general purpose constructor. + */ + + //chat global is using leader channel protocolMsg now. + public ChatGlobalMsg(AbstractWorldObject source, String message) { + super(Protocol.LEADERCHANNELMESSAGE, source, message); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatGlobalMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LEADERCHANNELMESSAGE, origin, reader); + } + + /** + * Copy constructor + */ + public ChatGlobalMsg(ChatGlobalMsg msg) { + super(msg); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + + this.message = reader.getString(); + this.sourceName = reader.getString(); + + this.unknown02 = reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.unknown01); + writer.putString(this.message); + if (this.source == null) { + // TODO log error here + writer.putString(""); + writer.putInt(0); + } else { + writer.putString(((AbstractCharacter) this.source).getFirstName()); + writer.putInt(WorldServer.worldMapID); + } + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } +} diff --git a/src/engine/net/client/msg/chat/ChatGroupMsg.java b/src/engine/net/client/msg/chat/ChatGroupMsg.java new file mode 100644 index 00000000..15bbe6a1 --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatGroupMsg.java @@ -0,0 +1,75 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ChatGroupMsg extends AbstractChatMsg { + + /** + * This is the general purpose constructor. + */ + public ChatGroupMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATGROUP, source, message); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatGroupMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATGROUP, origin, reader); + } + + /** + * Copy constructor + */ + public ChatGroupMsg(ChatGroupMsg msg) { + super(msg); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + + this.message = reader.getString(); + this.sourceName = reader.getString(); + + this.unknown02 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.unknown01); + + writer.putString(this.message); + writer.putString(this.sourceName); + + writer.putInt(this.unknown02); + } + +} diff --git a/src/engine/net/client/msg/chat/ChatGuildMsg.java b/src/engine/net/client/msg/chat/ChatGuildMsg.java new file mode 100644 index 00000000..8974654e --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatGuildMsg.java @@ -0,0 +1,149 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ChatGuildMsg extends AbstractChatMsg { + + protected int unknown03; + protected int unknown04; + protected int unknown05; + protected int unknown06; + + /** + * This is the general purpose constructor. + */ + public ChatGuildMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATGUILD, source, message); + this.unknown03 = 0; + this.unknown04 = 0; + this.unknown05 = 0; + this.unknown06 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatGuildMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATGUILD, origin, reader); + } + + /** + * Copy constructor + */ + public ChatGuildMsg(ChatGuildMsg msg) { + super(msg); + this.unknown03 = msg.unknown03; + this.unknown04 = msg.unknown04; + this.unknown05 = msg.unknown05; + this.unknown06 = msg.unknown06; + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + this.unknown02 = reader.getInt(); + this.sourceName = reader.getString(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.getInt(); + } + + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.unknown01); + writer.putString(this.message); + writer.putInt(this.unknown02); + writer.putString(this.sourceName); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + writer.putInt(this.unknown06); + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public int getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + /** + * @return the unknown06 + */ + public int getUnknown06() { + return unknown06; + } + + /** + * @param unknown06 the unknown06 to set + */ + public void setUnknown06(int unknown06) { + this.unknown06 = unknown06; + } + +} diff --git a/src/engine/net/client/msg/chat/ChatICMsg.java b/src/engine/net/client/msg/chat/ChatICMsg.java new file mode 100644 index 00000000..b2d3dfe4 --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatICMsg.java @@ -0,0 +1,129 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ChatICMsg extends AbstractChatMsg { + + protected int unknown03; + protected int unknown04; + protected int unknown05; + + /** + * This is the general purpose constructor. + */ + public ChatICMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATIC, source, message); + this.unknown03 = 0; + this.unknown04 = 0; + this.unknown05 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatICMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATIC, origin, reader); + } + + /** + * Copy constructor + */ + public ChatICMsg(ChatICMsg msg) { + super(msg); + this.unknown03 = msg.unknown03; + this.unknown04 = msg.unknown04; + this.unknown05 = msg.unknown05; + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + this.sourceName = reader.getString(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.unknown01); + writer.putString(this.message); + writer.putString(this.sourceName); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public int getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + +} \ No newline at end of file diff --git a/src/engine/net/client/msg/chat/ChatInfoMsg.java b/src/engine/net/client/msg/chat/ChatInfoMsg.java new file mode 100644 index 00000000..5645e8d0 --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatInfoMsg.java @@ -0,0 +1,60 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ChatInfoMsg extends AbstractChatMsg { + + /** + * This is the general purpose constructor. + */ + public ChatInfoMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATINFO, source, message); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatInfoMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATINFO, origin, reader); + } + + /** + * Copy constructor + */ + public ChatInfoMsg(ChatInfoMsg msg) { + super(msg); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + // TODO Implement Serialization + } + +} diff --git a/src/engine/net/client/msg/chat/ChatLeaderMsg.java b/src/engine/net/client/msg/chat/ChatLeaderMsg.java new file mode 100644 index 00000000..7f83f1ed --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatLeaderMsg.java @@ -0,0 +1,147 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ChatLeaderMsg extends AbstractChatMsg { + + protected int unknown03; + protected int unknown04; + protected int unknown05; + protected int unknown06; + + /** + * This is the general purpose constructor. + */ + public ChatLeaderMsg(AbstractWorldObject source, String message) { + super(Protocol.LEADERCHANNELMESSAGE, source, message); + this.unknown03 = 0; + this.unknown04 = 0; + this.unknown05 = 0; + this.unknown06 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatLeaderMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LEADERCHANNELMESSAGE, origin, reader); + } + + /** + * Copy constructor + */ + public ChatLeaderMsg(ChatLeaderMsg msg) { + super(msg); + this.unknown03 = msg.unknown03; + this.unknown04 = msg.unknown04; + this.unknown05 = msg.unknown05; + this.unknown06 = msg.unknown06; + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + this.sourceName = reader.getString(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.getInt(); + } + + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.unknown01); + writer.putString(this.message); + writer.putString(this.sourceName); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + writer.putInt(this.unknown06); + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public int getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + /** + * @return the unknown06 + */ + public int getUnknown06() { + return unknown06; + } + + /** + * @param unknown06 the unknown06 to set + */ + public void setUnknown06(int unknown06) { + this.unknown06 = unknown06; + } + +} diff --git a/src/engine/net/client/msg/chat/ChatPvPMsg.java b/src/engine/net/client/msg/chat/ChatPvPMsg.java new file mode 100644 index 00000000..a84610af --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatPvPMsg.java @@ -0,0 +1,86 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ChatPvPMsg extends AbstractChatMsg { + + protected int unknown03; + + /** + * This is the general purpose constructor. + */ + public ChatPvPMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATPVP, source, message); + this.unknown03 = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatPvPMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATPVP, origin, reader); + } + + /** + * Copy constructor + */ + public ChatPvPMsg(ChatPvPMsg msg) { + super(msg); + this.unknown03 = msg.unknown03; + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.message = reader.getString(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(0); // Probably Type + writer.putInt(0); // Probably objectUUID + writer.putInt(0); + + writer.putString(this.message); + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + +} diff --git a/src/engine/net/client/msg/chat/ChatSayMsg.java b/src/engine/net/client/msg/chat/ChatSayMsg.java new file mode 100644 index 00000000..a50e93d0 --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatSayMsg.java @@ -0,0 +1,90 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + +import engine.Enum.GameObjectType; +import engine.gameManager.SessionManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractGameObject; +import engine.objects.AbstractWorldObject; +import engine.server.world.WorldServer; + +public class ChatSayMsg extends AbstractChatMsg { + + /** + * This is the general purpose constructor. + */ + public ChatSayMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATSAY, source, message); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatSayMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATSAY, origin, reader); + } + + /** + * Copy constructor + */ + public ChatSayMsg(ChatSayMsg msg) { + super(msg); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + long sourceID = reader.getLong(); + this.unknown01 = reader.getInt(); + + int objectUUID = AbstractGameObject.extractUUID(GameObjectType.PlayerCharacter, sourceID); + this.source = SessionManager.getPlayerCharacterByID(objectUUID); + + // this.unknown01 = reader.getInt(); //not needed? + this.message = reader.getString(); + + this.sourceName = reader.getString(); + this.unknown02 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + if (this.source != null) { + writer.putInt(this.source.getObjectType().ordinal()); + writer.putInt(source.getObjectUUID()); + } else + writer.putLong(0L); + writer.putInt(0); + writer.putString(this.message); + if (this.source == null) { + // TODO log error here + writer.putString(""); + writer.putInt(0); + } else { + writer.putString(((AbstractCharacter) this.source).getFirstName()); + writer.putInt(WorldServer.worldMapID); + } + } +} diff --git a/src/engine/net/client/msg/chat/ChatShoutMsg.java b/src/engine/net/client/msg/chat/ChatShoutMsg.java new file mode 100644 index 00000000..761f13dd --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatShoutMsg.java @@ -0,0 +1,80 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.server.world.WorldServer; + +public class ChatShoutMsg extends AbstractChatMsg { + + /** + * This is the general purpose constructor. + */ + public ChatShoutMsg(AbstractWorldObject source, String message) { + super(Protocol.CHATSHOUT, source, message); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatShoutMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATSHOUT, origin, reader); + } + + /** + * Copy constructor + */ + public ChatShoutMsg(ChatShoutMsg msg) { + super(msg); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + + this.message = reader.getString(); + this.sourceName = reader.getString(); + + this.unknown02 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.unknown01); + writer.putString(this.message); + if (this.source == null) { + // TODO log error here + writer.putString(""); + writer.putInt(0); + } else { + writer.putString(((AbstractCharacter) this.source).getFirstName()); + writer.putInt(WorldServer.worldMapID); + } + } +} diff --git a/src/engine/net/client/msg/chat/ChatSystemChannelMsg.java b/src/engine/net/client/msg/chat/ChatSystemChannelMsg.java new file mode 100644 index 00000000..30e1f1a9 --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatSystemChannelMsg.java @@ -0,0 +1,158 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; + +public class ChatSystemChannelMsg extends AbstractChatMsg { + + // TODO enum this? + + // messageType + // 1 = Error + // 2 = Info + // 3 = Message of the Day + protected int messageType; + + protected int unknown03; + protected int unknown04; + + protected int channel; + + /** + * This is the general purpose constructor. + */ + public ChatSystemChannelMsg(AbstractWorldObject source, String message, int messageType) { + super(Protocol.SYSTEMCHANNEL, source, message); + this.channel = 0; + this.messageType = messageType; + this.unknown03 = 0; + this.unknown04 = 0; + } + + /** + * Copy constructor + */ + public ChatSystemChannelMsg(ChatSystemChannelMsg msg) { + super(msg); + this.messageType = msg.messageType; + this.unknown03 = msg.unknown03; + this.unknown04 = msg.unknown04; + this.channel = msg.channel; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatSystemChannelMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SYSTEMCHANNEL, origin, reader); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.channel = reader.getInt(); + this.messageType = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.message = reader.getString(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); // seems to alternate beween 0x0 and + // 0x40C30000 + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.channel); + writer.putInt(this.messageType); + if (this.source != null) + ;// writer.putLong(this.source.getCompositeID()); + else + // writer.putLong(0L); + writer.putString(this.message); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + + /* + * for (String key : this.vars.keySet()) { writer.putString(key); + * writer.putString(this.vars.get(key)); } + */ + } + + /** + * @return the channel + */ + public int getChannel() { + return channel; + } + + /** + * @param channel the channel to set + */ + public void setChannel(int channel) { + this.channel = channel; + } + + /** + * @return the messageType + */ + public int getMessageType() { + return messageType; + } + + /** + * @param messageType the messageType to set + */ + public void setMessageType(int messageType) { + this.messageType = messageType; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + +} diff --git a/src/engine/net/client/msg/chat/ChatSystemMsg.java b/src/engine/net/client/msg/chat/ChatSystemMsg.java new file mode 100644 index 00000000..29b412fa --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatSystemMsg.java @@ -0,0 +1,199 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractWorldObject; +import engine.server.MBServerStatics; + +import java.util.concurrent.ConcurrentHashMap; + + +public class ChatSystemMsg extends AbstractChatMsg { + + // TODO enum this? + + // channel + // 1 = System + // 2 = General Announcement (Flashing at top of screen!) + // 3 = Commander + // 5 = Nation + // 6 = Leader + // 7 = Shout + // 8 = Siege + // 9 = Territory + // 10 = Info + // 12 = Guild + // 13 = Inner Council + // 14 = Group + // 15 = City + // 16 = say + // 17 = Emote + // 19 = tell + protected int channel; + + // messageType + // 1 = Error + // 2 = Info + // 3 = Message of the Day + protected int messageType; + + protected int unknown03; + protected int numVariables; + // TODO this doesn't need to be a global value, + // its inherit to the HashMap + protected ConcurrentHashMap vars; + + // TODO make a list of variables + + /** + * This is the general purpose constructor. + */ + public ChatSystemMsg(AbstractWorldObject source, String message) { + super(Protocol.SYSTEMBROADCASTCHANNEL, source, message); + this.messageType = 2; + this.unknown03 = 0; + this.numVariables = 0; + vars = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatSystemMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SYSTEMBROADCASTCHANNEL, origin, reader); + } + + /** + * Copy constructor + */ + public ChatSystemMsg(ChatSystemMsg msg) { + super(msg); + this.channel = msg.channel; + this.messageType = msg.messageType; + this.unknown03 = msg.unknown03; + this.numVariables = msg.numVariables; + this.vars = msg.vars; + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.vars = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + this.channel = reader.getInt(); + this.messageType = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + + this.message = reader.getString(); + this.unknown03 = reader.getInt(); + this.numVariables = reader.getInt(); + + for (int i = 0; i < this.numVariables; ++i) { + String key = reader.getString(); + String value = reader.getString(); + reader.get(); + this.vars.put(key, value); + } + + } + + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.channel); + writer.putInt(this.messageType); + if (this.source != null) { + writer.putInt(source.getObjectType().ordinal()); + writer.putInt(source.getObjectUUID()); + } else + writer.putLong(0L); + writer.putString(this.message); + writer.putInt(this.unknown03); + writer.putInt(this.numVariables); + + for (String key : this.vars.keySet()) { + writer.putString(key); + writer.putString(this.vars.get(key)); + } + } + + /** + * @return the channel + */ + public int getChannel() { + return channel; + } + + /** + * @param channel the channel to set + */ + public void setChannel(int channel) { + this.channel = channel; + } + + /** + * @return the messageType + */ + public int getMessageType() { + return messageType; + } + + /** + * @param messageType the messageType to set + */ + public void setMessageType(int messageType) { + this.messageType = messageType; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the numVariables + */ + public int getNumVariables() { + return numVariables; + } + + + /** + * @return the vars + */ + public ConcurrentHashMap getVars() { + return vars; + } + + +} diff --git a/src/engine/net/client/msg/chat/ChatTellMsg.java b/src/engine/net/client/msg/chat/ChatTellMsg.java new file mode 100644 index 00000000..fe60e679 --- /dev/null +++ b/src/engine/net/client/msg/chat/ChatTellMsg.java @@ -0,0 +1,166 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.server.world.WorldServer; + +public class ChatTellMsg extends AbstractChatMsg { + + protected AbstractWorldObject target; + protected int targetType; + protected int targetID; + protected String targetName; + protected int unknown03; + + /** + * This is the general purpose constructor. + */ + public ChatTellMsg(AbstractWorldObject source, AbstractWorldObject target, String message) { + super(Protocol.CHATTELL, source, message); + this.target = target; + + // TODO could this be simplified? Check serializer; + if (this.target != null) { + this.targetType = target.getObjectType().ordinal(); + this.targetID = target.getObjectUUID(); + this.targetName = target.getName(); + } + this.unknown03 = 0; + } + + /** + * Copy constructor + */ + public ChatTellMsg(ChatTellMsg msg) { + super(msg); + this.target = msg.target; + this.targetType = msg.targetType; + this.targetID = msg.targetID; + this.targetName = msg.targetName; + this.unknown03 = msg.unknown03; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChatTellMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHATTELL, origin, reader); + } + + public int getTargetType() { + return targetType; + } + + public int getTargetID() { + return targetID; + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + this.sourceName = reader.getString(); // sourceName + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.targetName = reader.getString(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.source.getObjectType().ordinal()); + writer.putInt(source.getObjectUUID()); + writer.putInt(this.unknown01); + writer.putString(this.message); + if (AbstractWorldObject.IsAbstractCharacter(source)) { + writer.putString(((AbstractCharacter) this.source).getFirstName()); + } else { + writer.putString(this.source.getName()); + } + if (this.target != null) { + writer.putInt(this.target.getObjectType().ordinal()); + writer.putInt(this.target.getObjectUUID()); + if (AbstractWorldObject.IsAbstractCharacter(target)) { + writer.putString(((AbstractCharacter) this.target).getFirstName()); + } else { + writer.putString(this.target.getName()); + } + } else { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putString(this.targetName); + } + writer.putInt(WorldServer.worldMapID); + writer.putInt(WorldServer.worldMapID); + } + + /** + * @return the target + */ + public AbstractWorldObject getTarget() { + return target; + } + + /** + * @param target the target to set + */ + public void setTarget(AbstractWorldObject target) { + this.target = target; + } + + + /** + * @return the targetName + */ + public String getTargetName() { + return targetName; + } + + /** + * @param targetName the targetName to set + */ + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + +} diff --git a/src/engine/net/client/msg/chat/GuildEnterWorldMsg.java b/src/engine/net/client/msg/chat/GuildEnterWorldMsg.java new file mode 100644 index 00000000..586cfad3 --- /dev/null +++ b/src/engine/net/client/msg/chat/GuildEnterWorldMsg.java @@ -0,0 +1,237 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.chat; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.objects.PlayerCharacter; + +public class GuildEnterWorldMsg extends AbstractChatMsg { + + protected String name; + protected int guildTitle; + protected int charterType; + protected int unknown04; + protected int guildUUID; + protected int unknown05; + protected int unknown06; + + /** + * This is the general purpose constructor. + */ + public GuildEnterWorldMsg(PlayerCharacter source) { + super(Protocol.GUILDMEMBERONLINE, source, "[!PLAYERRANK!] !PLAYERNAME! is online"); + + this.name = ""; + this.guildTitle = 0; + this.charterType = 9; + this.unknown02 = 12; + this.unknown04 = 2; + this.guildUUID = 0; + this.unknown05 = 0x2E46D00C; + this.unknown06 = 0; + } + + /** + * Copy constructor + */ + public GuildEnterWorldMsg(GuildEnterWorldMsg msg) { + super(msg); + this.name = msg.name; + this.guildTitle = msg.guildTitle; + this.charterType = msg.charterType; + this.unknown04 = msg.unknown04; + this.guildUUID = msg.guildUUID; + this.unknown05 = msg.unknown05; + this.unknown06 = msg.unknown06; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildEnterWorldMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GUILDMEMBERONLINE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.name); + + writer.putInt(guildTitle); + writer.put((byte) 1); // forgot this + writer.putInt(this.charterType); + writer.putInt(this.unknown02); + writer.putInt(this.unknown04); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.guildUUID); + + writer.putString(this.message); + writer.putInt(this.unknown05); + writer.putInt(this.unknown06); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.name = reader.getString(); + this.guildTitle = reader.getInt(); + reader.get(); // forgot this + this.unknown02 = reader.getInt(); + this.charterType = reader.getInt(); + this.unknown04 = reader.getInt(); + reader.getInt(); // Object Type Padding + this.guildUUID = reader.getInt(); + this.message = reader.getString(); + this.unknown05 = reader.getInt(); + this.unknown06 = reader.getInt(); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the guildTitle + */ + public int getGuildTitle() { + return guildTitle; + } + + /** + * @param guildTitle the guildTitle to set + */ + public void setGuildTitle(int guildTitle) { + this.guildTitle = guildTitle; + } + + /** + * @return the unknown02 + */ + @Override + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + @Override + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public int getCharter() { + return charterType; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setCharter(int charter) { + this.charterType = charter; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the guildUUID + */ + public int getGuildUUID() { + return guildUUID; + } + + /** + * @param guildUUID the guildUUID to set + */ + public void setGuildUUID(int guildUUID) { + this.guildUUID = guildUUID; + } + + /** + * @return the message + */ + @Override + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + @Override + public void setMessage(String message) { + this.message = message; + } + + /** + * @return the unknown05 + */ + public int getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + /** + * @return the unknown06 + */ + public int getUnknown06() { + return unknown06; + } + + /** + * @param unknown06 the unknown06 to set + */ + public void setUnknown06(int unknown06) { + this.unknown06 = unknown06; + } + +} diff --git a/src/engine/net/client/msg/commands/ClientAdminCommandMsg.java b/src/engine/net/client/msg/commands/ClientAdminCommandMsg.java new file mode 100644 index 00000000..3eb3025c --- /dev/null +++ b/src/engine/net/client/msg/commands/ClientAdminCommandMsg.java @@ -0,0 +1,101 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.commands; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.math.Vector3f; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.AbstractGameObject; + +public class ClientAdminCommandMsg extends ClientNetMsg { + + private int msgType; + private String msgCommand; + private long senderCompID; + private int targetType; + private int targetUUID; + private Vector3f location; + private AbstractGameObject target; + + /** + * This is the general purpose constructor. + */ + public ClientAdminCommandMsg() { + super(Protocol.CLIENTADMINCOMMAND); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ClientAdminCommandMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CLIENTADMINCOMMAND, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + // TODO implement Serialize() + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.msgType = reader.getInt(); + this.msgCommand = reader.getString(); + this.targetType = reader.getInt(); + this.targetUUID = reader.getInt(); + this.senderCompID = reader.getLong(); // always null?? + this.location = new Vector3f(reader.getFloat(), reader.getFloat(), reader.getFloat()); + + if (targetUUID != 0) + target = DbManager.getObject(GameObjectType.values()[this.targetType], this.targetUUID); + } + + /** + * @return the msgCommand + */ + public String getMsgCommand() { + return msgCommand; + } + + /** + * @return the targetUUID + */ + public int getTargetUUID() { + return targetUUID; + } + + /** + * @return the location + */ + public Vector3f getLocation() { + return location; + } + + /** + * @return the target + */ + public AbstractGameObject getTarget() { + return target; + } + +} diff --git a/src/engine/net/client/msg/group/AppointGroupLeaderMsg.java b/src/engine/net/client/msg/group/AppointGroupLeaderMsg.java new file mode 100644 index 00000000..d3d4013b --- /dev/null +++ b/src/engine/net/client/msg/group/AppointGroupLeaderMsg.java @@ -0,0 +1,125 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class AppointGroupLeaderMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + private int response; + private String message; + + /** + * This is the general purpose constructor. + */ + public AppointGroupLeaderMsg() { + super(Protocol.GROUPLEADERAPPOINT); + this.message = "You are already the group leader"; + + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AppointGroupLeaderMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GROUPLEADERAPPOINT, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.response); + if (this.response == 1) + writer.putString(this.message); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.response = reader.getInt(); + if (this.response == 1) + this.message = reader.getString(); + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @param targetType the targetType to set + */ + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + /** + * @param targetID the targetID to set + */ + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + /** + * @return the response + */ + public int getResponse() { + return response; + } + + /** + * @param response the response to set + */ + public void setResponse(int response) { + this.response = response; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/engine/net/client/msg/group/DisbandGroupMsg.java b/src/engine/net/client/msg/group/DisbandGroupMsg.java new file mode 100644 index 00000000..b23ae529 --- /dev/null +++ b/src/engine/net/client/msg/group/DisbandGroupMsg.java @@ -0,0 +1,67 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class DisbandGroupMsg extends ClientNetMsg { + + private int unknown01; + + /** + * This is the general purpose constructor. + */ + public DisbandGroupMsg() { + super(Protocol.GROUPDISBAND); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public DisbandGroupMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GROUPDISBAND, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + +} diff --git a/src/engine/net/client/msg/group/FormationFollowMsg.java b/src/engine/net/client/msg/group/FormationFollowMsg.java new file mode 100644 index 00000000..afd2acc4 --- /dev/null +++ b/src/engine/net/client/msg/group/FormationFollowMsg.java @@ -0,0 +1,108 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class FormationFollowMsg extends ClientNetMsg { + + private boolean follow; + private int formation; + private int unknown01; + + /** + * This is the general purpose constructor. + */ + public FormationFollowMsg() { + super(Protocol.GROUPFOLLOW); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public FormationFollowMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GROUPFOLLOW, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + if (this.follow) { + writer.putInt(1); + writer.putInt(0); + } else { + writer.putInt(0); + writer.putInt(1); + } + writer.putInt(this.formation); + writer.putInt(this.unknown01); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.follow = reader.getInt() == 1; + reader.getInt(); + this.formation = reader.getInt(); + this.unknown01 = reader.getInt(); + } + + /** + * @return the follow + */ + public boolean isFollow() { + return follow; + } + + /** + * @param follow the follow to set + */ + public void setFollow(boolean follow) { + this.follow = follow; + } + + /** + * @return the formation + */ + public int getFormation() { + return formation; + } + + /** + * @param formation the formation to set + */ + public void setFormation(int formation) { + this.formation = formation; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + +} diff --git a/src/engine/net/client/msg/group/GroupInviteMsg.java b/src/engine/net/client/msg/group/GroupInviteMsg.java new file mode 100644 index 00000000..f6afdf00 --- /dev/null +++ b/src/engine/net/client/msg/group/GroupInviteMsg.java @@ -0,0 +1,211 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class GroupInviteMsg extends ClientNetMsg { + + private int sourceType; + private int sourceID; + private int targetType; + private int targetID; + private int groupType; + private int groupID; + private int unknown01; + private int invited; + private String name; + + /** + * This is the general purpose constructor. + */ + public GroupInviteMsg() { + super(Protocol.INVITEGROUP); + this.name = ""; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GroupInviteMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.INVITEGROUP, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.sourceType); + writer.putInt(this.sourceID); + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.groupType); + writer.putInt(this.groupID); + writer.putInt(this.unknown01); + writer.putInt(this.invited); + if (this.invited == 1) + writer.putString(this.name); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + this.sourceType = reader.getInt(); + this.sourceID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.groupType = reader.getInt(); + this.groupID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.invited = reader.getInt(); + if (this.invited == 1) + this.name = reader.getString(); + + } + + /** + * @return the sourceType + */ + public int getSourceType() { + return sourceType; + } + + /** + * @param sourceType the sourceType to set + */ + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + /** + * @return the sourceID + */ + public int getSourceID() { + return sourceID; + } + + /** + * @param sourceID the sourceID to set + */ + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @param targetType the targetType to set + */ + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + /** + * @param targetID the targetID to set + */ + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + /** + * @return the groupType + */ + public int getGroupType() { + return groupType; + } + + /** + * @param groupType the groupType to set + */ + public void setGroupType(int groupType) { + this.groupType = groupType; + } + + /** + * @return the groupID + */ + public int getGroupID() { + return groupID; + } + + /** + * @param groupID the groupID to set + */ + public void setGroupID(int groupID) { + this.groupID = groupID; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the invited + */ + public int getInvited() { + return invited; + } + + /** + * @param invited the invited to set + */ + public void setInvited(int invited) { + this.invited = invited; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + +} diff --git a/src/engine/net/client/msg/group/GroupInviteResponseMsg.java b/src/engine/net/client/msg/group/GroupInviteResponseMsg.java new file mode 100644 index 00000000..c9e26bf9 --- /dev/null +++ b/src/engine/net/client/msg/group/GroupInviteResponseMsg.java @@ -0,0 +1,118 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class GroupInviteResponseMsg extends ClientNetMsg { + + private int groupType; + private int groupID; + private int unknown01; + private int unknown02; + + /** + * This is the general purpose constructor. + */ + public GroupInviteResponseMsg() { + super(Protocol.JOINGROUP); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public GroupInviteResponseMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.JOINGROUP, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.groupType); + writer.putInt(this.groupID); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.groupType = reader.getInt(); + this.groupID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + } + + /** + * @return the groupType + */ + public int getGroupType() { + return groupType; + } + + /** + * @param groupType the groupType to set + */ + public void setGroupType(int groupType) { + this.groupType = groupType; + } + + /** + * @return the groupID + */ + public int getGroupID() { + return groupID; + } + + /** + * @param groupID the groupID to set + */ + public void setGroupID(int groupID) { + this.groupID = groupID; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + +} diff --git a/src/engine/net/client/msg/group/GroupUpdateMsg.java b/src/engine/net/client/msg/group/GroupUpdateMsg.java new file mode 100644 index 00000000..6b9393dc --- /dev/null +++ b/src/engine/net/client/msg/group/GroupUpdateMsg.java @@ -0,0 +1,353 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + +import engine.Enum.GameObjectType; +import engine.gameManager.GroupManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Group; +import engine.objects.PlayerCharacter; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +//See GroupUpdateMsgBreakdown.txt is SBData directory. +public class GroupUpdateMsg extends ClientNetMsg { + + private int messageType; + private int unknown02; + private int playerUUID; + private Set players; + private Group group; + + /** + * This is the general purpose constructor. + */ + public GroupUpdateMsg() { + super(Protocol.UPDATEGROUP); + this.messageType = 1; + this.unknown02 = 1; + this.playerUUID = 0; + this.players = Collections.newSetFromMap(new ConcurrentHashMap<>()); + } + + public GroupUpdateMsg(int messageType, int unknown02, Set players, Group group) { + super(Protocol.UPDATEGROUP); + this.messageType = messageType; + this.unknown02 = unknown02; + this.playerUUID = 0; + this.players = players; + this.group = group; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GroupUpdateMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATEGROUP, origin, reader); + } + + private static void _serializeFour(ByteBufferWriter writer) { + + // 4 sends a party dissolved window + for (int i = 0; i < 3; i++) { + writer.putInt(0); + } + } + + // *** Refactor: This method is an abortion. Needs to be re-written from scratch. + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.Group.ordinal()); + writer.putInt(this.group.getObjectUUID()); + writer.putInt(this.messageType); + + // 5 breaks everything including movement etc + // 4 sends a party dissolved message + // 3 closes the group window and leaves the group + // 2 seems to update the location but not the stats correctly upon coming back into range + // 1 seems to add you to the group but if called by a job tops up your stats on the client and desyncs it + + switch (messageType) { + case 4: + GroupUpdateMsg._serializeFour(writer); + break; + case 5: + this._serializeFive(writer); + break; + case 6: + this._serializeSix(writer); + break; + case 7: + this._serializeSeven(writer); + break; + case 8: + this._serializeEight(writer); + break; + default: + writer.putInt(this.unknown02); + // Send player data + writer.putInt(this.players.size()); + int i = 0; + for (PlayerCharacter pc : this.players) { + this.serializePlayer(writer, pc, this.messageType, i++); + } + writer.putInt(0); // end data + break; + } + } + + private void serializePlayer(ByteBufferWriter writer, PlayerCharacter player, int messageType, int count) { + + if (messageType == 1) { + writer.putString((player != null) ? player.getFirstName() : "nullError"); + writer.putString((player != null) ? player.getLastName() : ""); + } else if (messageType == 2) { + if (count == 0) { + writer.putString((player != null) ? player.getFirstName() : "nullError"); + writer.putString((player != null) ? player.getLastName() : ""); + } else { + writer.putInt(0); + } + } else if (messageType == 3) { + writer.putString(" "); + writer.putString(" "); + } else { + writer.putInt(0); + writer.putInt(0); + } + + if (messageType == 3) { + for (int i = 0; i < 6; i++) { + writer.putInt(0); + } + } else { + // mana health stam % + writer.putInt((int) (player.getHealth() / player.getHealthMax() * 100)); // should be health but does nothing + writer.putInt((int) (player.getStamina() / player.getStaminaMax() * 100)); // stam % + writer.putInt((int) (player.getMana() / player.getManaMax() * 100)); // mana % + writer.putInt((player != null) ? Float.floatToIntBits(player.getLoc().getX()) : 0); + writer.putInt((player != null) ? Float.floatToIntBits(player.getLoc().getY()) : 0); + writer.putInt((player != null) ? Float.floatToIntBits(player.getLoc().getZ()) : 0); + } + + if (player == null) + writer.putLong(0); + else { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(player.getObjectUUID()); + } + + if (messageType == 3) { + writer.putInt(0); + writer.putInt(-1); + writer.putInt(0); + return; + } else if (messageType == 5) { + writer.putInt(0); + writer.putInt(0); + return; + } + if (group != null && player != null) { + writer.putInt((this.group.getGroupLead() == player) ? 2 : 1); + } else { + writer.putInt(1); + } + if (messageType == 2) { + writer.putInt(-1); + writer.putInt(0); + return; + } + writer.putInt(1); + writer.putInt(1); + writer.put((byte) 1); + + // if sending message type 1 this seems to make the group window flicker the button + // i think getfollow and split gold might be the wrong way around + if (group != null) { + writer.put(this.group.getSplitGold() ? (byte) 1 : (byte) 0); + } else { + writer.put((byte) 0); + } + + // always gets reset on a message type 1 + if (player != null) { + writer.put(player.getFollow() ? (byte) 1 : (byte) 0); + } else { + writer.put((byte) 0); + } + } + + //sync player's stats and position + private void _serializeFive(ByteBufferWriter writer) { + writer.putInt(1); + writer.putInt(players.size() - 1); + for (PlayerCharacter player : players) { + if (player.getObjectUUID() == this.playerUUID) { + continue; //skip self + } + writer.putInt((int) (player.getHealth() / player.getHealthMax() * 100)); + writer.putInt((int) (player.getStamina() / player.getStaminaMax() * 100)); + writer.putInt((int) (player.getMana() / player.getManaMax() * 100)); + writer.putFloat(player.getLoc().x); + writer.putFloat(player.getLoc().y); + writer.putFloat(player.getLoc().z); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(player.getObjectUUID()); + + } + writer.putInt(0); + writer.putInt(0); + } + + private void _serializeSix(ByteBufferWriter writer) { + writer.putInt(0); + if (this.group != null) { + writer.put(this.group.getSplitGold() ? (byte) 1 : (byte) 0); + } else { + writer.put((byte) 0); + } + writer.putInt(0); + writer.putInt(0); + } + + private void _serializeSeven(ByteBufferWriter writer) { + PlayerCharacter player = this.players.iterator().next(); + writer.putInt(0); + if (player != null) { + writer.put(player.getFollow() ? (byte) 1 : (byte) 0); + } else { + writer.put((byte) 0); + } + writer.putInt(0); + writer.putInt(0); + } + + private void _serializeEight(ByteBufferWriter writer) { + PlayerCharacter player = this.players.iterator().next(); + ; + writer.putInt(0); + if (player != null) { + writer.put(player.getFollow() ? (byte) 1 : (byte) 0); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(player.getObjectUUID()); + } else { + writer.put((byte) 0); + writer.putLong(0L); + } + writer.putInt(0); + writer.putInt(0); + } + + /** + * Deserializes the subclass specific items from the supplied + * ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.players = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + reader.getInt(); + this.group = GroupManager.getGroup(reader.getInt()); + // TODO figure this mess out + } + + public void addPlayer(PlayerCharacter value) { + this.players.add(value); + } + + public void setPlayer(PlayerCharacter value) { + this.players.clear(); + this.players.add(value); + } + + /** + * @return the messageType + */ + public int getMessageType() { + return messageType; + } + + /** + * @param messageType the messageType to set + */ + public void setMessageType(int messageType) { + this.messageType = messageType; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the playerUUID + */ + public long getPlayerID() { + return playerUUID; + } + + /** + * @param playerUUID the playerUUID to set + */ + public void setPlayerUUID(int playerUUID) { + this.playerUUID = playerUUID; + } + + /** + * @return the players + */ + public Set getPlayers() { + return players; + } + + /** + * @param players the players to set + */ + public void setPlayers(Set players) { + this.players = players; + } + + /** + * @return the group + */ + public Group getGroup() { + return group; + } + + /** + * @param group the group to set + */ + public void setGroup(Group group) { + this.group = group; + } + +} diff --git a/src/engine/net/client/msg/group/LeaveGroupMsg.java b/src/engine/net/client/msg/group/LeaveGroupMsg.java new file mode 100644 index 00000000..f477de3b --- /dev/null +++ b/src/engine/net/client/msg/group/LeaveGroupMsg.java @@ -0,0 +1,117 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class LeaveGroupMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int unknown03; + private int unknown04; + + /** + * This is the general purpose constructor. + */ + public LeaveGroupMsg() { + super(Protocol.LEAVEGROUP); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public LeaveGroupMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LEAVEGROUP, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } +} diff --git a/src/engine/net/client/msg/group/RemoveFromGroupMsg.java b/src/engine/net/client/msg/group/RemoveFromGroupMsg.java new file mode 100644 index 00000000..7ff6586a --- /dev/null +++ b/src/engine/net/client/msg/group/RemoveFromGroupMsg.java @@ -0,0 +1,125 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class RemoveFromGroupMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + private int response; + private String message; + + /** + * This is the general purpose constructor. + */ + public RemoveFromGroupMsg() { + super(Protocol.GROUPREMOVE); + + this.message = "Quit if you want to remove yourself"; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public RemoveFromGroupMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GROUPREMOVE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.response); + if (this.response == 1) + writer.putString(this.message); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.response = reader.getInt(); + if (this.response == 1) + this.message = reader.getString(); + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @param targetType the targetType to set + */ + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + /** + * @param targetID the targetID to set + */ + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + /** + * @return the response + */ + public int getResponse() { + return response; + } + + /** + * @param response the response to set + */ + public void setResponse(int response) { + this.response = response; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/engine/net/client/msg/group/ToggleGroupSplitMsg.java b/src/engine/net/client/msg/group/ToggleGroupSplitMsg.java new file mode 100644 index 00000000..1becdabb --- /dev/null +++ b/src/engine/net/client/msg/group/ToggleGroupSplitMsg.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.group; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class ToggleGroupSplitMsg extends ClientNetMsg { + + private int unknown01; + + /** + * This is the general purpose constructor. + */ + public ToggleGroupSplitMsg() { + super(Protocol.GROUPTREASURE); + this.unknown01 = 1; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ToggleGroupSplitMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GROUPTREASURE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + +} diff --git a/src/engine/net/client/msg/guild/AcceptInviteToGuildMsg.java b/src/engine/net/client/msg/guild/AcceptInviteToGuildMsg.java new file mode 100644 index 00000000..4e16f6ef --- /dev/null +++ b/src/engine/net/client/msg/guild/AcceptInviteToGuildMsg.java @@ -0,0 +1,107 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class AcceptInviteToGuildMsg extends ClientNetMsg { + + private int guildUUID; + private int unknown01; + private int unknown02; + + /** + * This is the general purpose constructor. + */ + public AcceptInviteToGuildMsg() { + super(Protocol.JOINGUILD); + } + + public AcceptInviteToGuildMsg(int guildUUID, int unknown01, int unknown02) { + super(Protocol.JOINGUILD); + this.guildUUID = guildUUID; + this.unknown01 = unknown01; + this.unknown02 = unknown02; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public AcceptInviteToGuildMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.JOINGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.guildUUID); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + } + + /** + * Deserializes the subclass specific items from the supplied + * ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); // Object Type padding + this.guildUUID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + } + + /** + * @return the guildUUID + */ + public int getGuildUUID() { + return guildUUID; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + +} diff --git a/src/engine/net/client/msg/guild/AcceptSubInviteMsg.java b/src/engine/net/client/msg/guild/AcceptSubInviteMsg.java new file mode 100644 index 00000000..4ff7c8f8 --- /dev/null +++ b/src/engine/net/client/msg/guild/AcceptSubInviteMsg.java @@ -0,0 +1,124 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class AcceptSubInviteMsg extends ClientNetMsg { + + private int guildUUID; + private int unknown01; + private int unknown02; + private String response; + + /** + * This is the general purpose constructor. + */ + public AcceptSubInviteMsg() { + super(Protocol.JOINFORPROVINCE); + } + + public AcceptSubInviteMsg(int guildUUID, int unknown01, int unknown02, String response) { + super(Protocol.JOINFORPROVINCE); + this.guildUUID = guildUUID; + this.unknown01 = unknown01; + this.unknown02 = unknown02; + this.response = response; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public AcceptSubInviteMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.JOINFORPROVINCE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.guildUUID); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putString(this.response); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); // Object Type Padding + this.guildUUID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + reader.getString(); + } + + /** + * @return the guildUUID + */ + public int guildUUID() { + return guildUUID; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + + public String getResponse() { + return this.response; + } + + public void setResponse(String value) { + this.response = value; + } +} diff --git a/src/engine/net/client/msg/guild/BanishUnbanishMsg.java b/src/engine/net/client/msg/guild/BanishUnbanishMsg.java new file mode 100644 index 00000000..d06af36c --- /dev/null +++ b/src/engine/net/client/msg/guild/BanishUnbanishMsg.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class BanishUnbanishMsg extends ClientNetMsg { + + private int target; + private int msgType; + + /** + * This is the general purpose constructor. + */ + public BanishUnbanishMsg() { + super(Protocol.BANISHMEMBER); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public BanishUnbanishMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.BANISHMEMBER, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + target = reader.getInt(); + + //Unknows? + reader.getInt(); + this.msgType = reader.getInt(); + reader.getInt(); + } + + public int getTarget() { + return target; + } + + public int getMsgType() { + return msgType; + } + + public void setMsgType(int msgType) { + this.msgType = msgType; + } +} diff --git a/src/engine/net/client/msg/guild/BreakFealtyMsg.java b/src/engine/net/client/msg/guild/BreakFealtyMsg.java new file mode 100644 index 00000000..7a003f63 --- /dev/null +++ b/src/engine/net/client/msg/guild/BreakFealtyMsg.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class BreakFealtyMsg extends ClientNetMsg { + + private int guildUUID; + + /** + * This is the general purpose constructor. + */ + public BreakFealtyMsg() { + super(Protocol.BREAKFEALTY); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public BreakFealtyMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.BREAKFEALTY, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); // Object Type Padding + this.guildUUID = reader.getInt(); + reader.getInt(); + } + + public int getGuildUUID() { + return guildUUID; + } + +} diff --git a/src/engine/net/client/msg/guild/ChangeRankMsg.java b/src/engine/net/client/msg/guild/ChangeRankMsg.java new file mode 100644 index 00000000..e6be2350 --- /dev/null +++ b/src/engine/net/client/msg/guild/ChangeRankMsg.java @@ -0,0 +1,133 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class ChangeRankMsg extends ClientNetMsg { + + private int playerUUID; + private int previousRank, newRank; + private byte ic, rec, tax; + private String errorMsg; + + /** + * This is the general purpose constructor. + */ + public ChangeRankMsg() { + super(Protocol.GUILDRANKCHANGE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ChangeRankMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.GUILDRANKCHANGE, origin, reader); + } + + public int getPlayerUUID() { + return playerUUID; + } + + public int getPreviousRank() { + return previousRank; + } + + public void setPreviousRank(int previousRank) { + this.previousRank = previousRank; + } + + public int getNewRank() { + return newRank; + } + + public void setNewRank(int newRank) { + this.newRank = newRank; + } + + public byte getIc() { + return ic; + } + + public void setIc(byte ic) { + this.ic = ic; + } + + public byte getRec() { + return rec; + } + + public void setRec(byte rec) { + this.rec = rec; + } + + public byte getTax() { + return tax; + } + + public void setTax(byte tax) { + this.tax = tax; + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.playerUUID); + writer.putInt(this.newRank); + writer.putInt(this.previousRank); + + writer.put(ic); + writer.put(tax); + writer.put(rec); + + writer.putInt(0); + + writer.putString(this.errorMsg); + } + + /** + * Deserializes the subclass specific items from the supplied + * ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); // Object Type Padding + this.playerUUID = reader.getInt(); + this.newRank = reader.getInt(); + this.previousRank = reader.getInt(); + this.ic = reader.get(); + this.tax = reader.get(); + this.rec = reader.get(); + + reader.getInt(); //Pad + + this.errorMsg = reader.getString(); + } +} diff --git a/src/engine/net/client/msg/guild/DisbandGuildMsg.java b/src/engine/net/client/msg/guild/DisbandGuildMsg.java new file mode 100644 index 00000000..f9f7cfd3 --- /dev/null +++ b/src/engine/net/client/msg/guild/DisbandGuildMsg.java @@ -0,0 +1,57 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class DisbandGuildMsg extends ClientNetMsg { + + private int response; + private String message; + + /** + * This is the general purpose constructor. + */ + public DisbandGuildMsg() { + super(Protocol.DISBANDGUILD); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public DisbandGuildMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.DISBANDGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.response); + writer.putString(this.message); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.response = reader.getInt(); + this.message = reader.getString(); + //this.sourceID = reader.getInt(); + } + +} diff --git a/src/engine/net/client/msg/guild/DismissGuildMsg.java b/src/engine/net/client/msg/guild/DismissGuildMsg.java new file mode 100644 index 00000000..5d67a966 --- /dev/null +++ b/src/engine/net/client/msg/guild/DismissGuildMsg.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class DismissGuildMsg extends ClientNetMsg { + + private int unknown01; + private int guildType; + private int guildID; + + /** + * This is the general purpose constructor. + */ + public DismissGuildMsg() { + super(Protocol.DISMISSGUILD); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public DismissGuildMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.DISMISSGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.guildType = reader.getInt(); + this.guildID = reader.getInt(); + reader.getInt(); + + } + + public int getUnknown01() { + return this.unknown01; + } + + + public void setUnknown01(int value) { + this.unknown01 = value; + } + + public int getGuildID() { + return guildID; + } + + public void setGuildID(int guildID) { + this.guildID = guildID; + } + +} diff --git a/src/engine/net/client/msg/guild/GuildControlMsg.java b/src/engine/net/client/msg/guild/GuildControlMsg.java new file mode 100644 index 00000000..8d938a1d --- /dev/null +++ b/src/engine/net/client/msg/guild/GuildControlMsg.java @@ -0,0 +1,232 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class GuildControlMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int unknown03; + private int unknown04; + private String message; + private byte unknown05; + private int unknown06; + private int unknown07; + private byte unknown08; + + private byte isGM; + + /** + * This is the general purpose constructor. + */ + public GuildControlMsg() { + super(Protocol.REQUESTMEMBERLIST); + this.unknown01 = 2; + this.unknown02 = 0; + this.unknown03 = 0; + this.unknown04 = 0; + this.message = "No error message"; + this.unknown05 = 0; + this.unknown06 = 0; + this.unknown07 = 257; + this.unknown08 = (byte) 1; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildControlMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTMEMBERLIST, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putString(this.message); + + writer.put((byte) 1); //Always 1 + + writer.put(isGM); //Can be Tax Collector + writer.put(isGM); //Can be Recruiter + writer.put(isGM); //Can be IC + writer.put(isGM); + + writer.put(isGM); //Can be GM + writer.put((byte) 1); + writer.put((byte) 1); + writer.put((byte) 1); + writer.put((byte) 1); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + this.unknown04 = reader.getInt(); + this.message = reader.getString(); + this.unknown05 = reader.get(); + if (this.unknown05 == (byte) 1) { + this.unknown06 = reader.getInt(); + this.unknown07 = reader.getInt(); + this.unknown08 = reader.get(); + } + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * @return the unknown05 + */ + public byte getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(byte unknown05) { + this.unknown05 = unknown05; + } + + /** + * @return the unknown06 + */ + public int getUnknown06() { + return unknown06; + } + + /** + * @param unknown06 the unknown06 to set + */ + public void setUnknown06(int unknown06) { + this.unknown06 = unknown06; + } + + /** + * @return the unknown07 + */ + public int getUnknown07() { + return unknown07; + } + + /** + * @param unknown07 the unknown07 to set + */ + public void setUnknown07(int unknown07) { + this.unknown07 = unknown07; + } + + /** + * @return the unknown08 + */ + public byte getUnknown08() { + return unknown08; + } + + /** + * @param unknown08 the unknown08 to set + */ + public void setUnknown08(byte unknown08) { + this.unknown08 = unknown08; + } + + public void setGM(byte b) { + this.isGM = b; + } + +} diff --git a/src/engine/net/client/msg/guild/GuildCreationCloseMsg.java b/src/engine/net/client/msg/guild/GuildCreationCloseMsg.java new file mode 100644 index 00000000..3a259238 --- /dev/null +++ b/src/engine/net/client/msg/guild/GuildCreationCloseMsg.java @@ -0,0 +1,67 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import org.pmw.tinylog.Logger; + +public class GuildCreationCloseMsg extends ClientNetMsg { + + + /** + * This is the general purpose constructor. + */ + public GuildCreationCloseMsg() { + super(Protocol.CANCELGUILDCREATION); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildCreationCloseMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CANCELGUILDCREATION, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + for (int j = 0; j < 4; j++) { + int i = reader.getInt(); + Logger.info(j + "->" + i); + } + } + +} + + + diff --git a/src/engine/net/client/msg/guild/GuildCreationFinalizeMsg.java b/src/engine/net/client/msg/guild/GuildCreationFinalizeMsg.java new file mode 100644 index 00000000..53f71710 --- /dev/null +++ b/src/engine/net/client/msg/guild/GuildCreationFinalizeMsg.java @@ -0,0 +1,160 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.GuildTag; +import engine.objects.Item; +import org.pmw.tinylog.Logger; + +public class GuildCreationFinalizeMsg extends ClientNetMsg { + + private GuildTag guildTag; + + private String guildName; + private String guildMotto; + + private int unknown01; //Appears to be a pad + private int unknown02; //Appears to be a pad + private int unknown03; //Appears to be a pad + + private int icVote; + private int membershipVote; + + private int unknown04; //Always -1 + private int unknown05; //Appears to be a pad + private int unknown06; //Appears to be a pad + private int subType; //Appears to be a pad + private int unknown08; //3 = close window, 4 = Failure to create Guild. + private boolean close = false; + private int charterUUID; + + /** + * This is the general purpose constructor. + */ + public GuildCreationFinalizeMsg() { + super(Protocol.CREATEPETITION); + } + + public GuildCreationFinalizeMsg(boolean close) { + super(Protocol.CREATEPETITION); + this.close = close; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildCreationFinalizeMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CREATEPETITION, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + GuildTag._serializeForDisplay(guildTag, writer); + + writer.putString(this.guildName); + writer.putString(this.guildMotto); + + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + + writer.putInt(this.membershipVote); + writer.putInt(this.icVote); + + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + + writer.putInt(this.unknown06); + writer.putInt(3); + writer.putInt(this.unknown08); + writer.putInt(GameObjectType.Item.ordinal()); + writer.putInt(this.charterUUID); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + guildTag = new GuildTag(reader, true); + + this.guildName = reader.getString(); + this.guildMotto = reader.getString(); + + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + + this.membershipVote = reader.getInt(); + this.icVote = reader.getInt(); + + this.unknown04 = reader.getInt(); + this.unknown05 = reader.getInt(); + + this.unknown06 = reader.getInt(); + this.subType = reader.getInt(); + this.unknown08 = reader.getInt(); + reader.getInt(); // Object Type padding + this.charterUUID = reader.getInt(); + } + + public String getName() { + return this.guildName; + } + + public String getMotto() { + return this.guildMotto; + } + + public Item getCharter() { + + Item charterObject; + + charterObject = Item.getFromCache(this.charterUUID); + + if (charterObject == null) + Logger.error("Invalid charter object UUID: " + this.charterUUID); + + return charterObject; + } + + public GuildTag getGuildTag() { + return this.guildTag; + } + + public int getMemberVoteFlag() { + + if (this.membershipVote != 0) + return 1; + + return 0; + } + + public int getICVoteFlag() { + + if (this.icVote != 0) + return 1; + + return 0; + } +} diff --git a/src/engine/net/client/msg/guild/GuildCreationOptionsMsg.java b/src/engine/net/client/msg/guild/GuildCreationOptionsMsg.java new file mode 100644 index 00000000..c22979b0 --- /dev/null +++ b/src/engine/net/client/msg/guild/GuildCreationOptionsMsg.java @@ -0,0 +1,152 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.GuildTag; + +public class GuildCreationOptionsMsg extends ClientNetMsg { + + private int screenType; + private ScreenType messageScreen; + private boolean close = false; + + /** + * This is the general purpose constructor. + */ + public GuildCreationOptionsMsg() { + super(Protocol.CHECKUNIQUEGUILD); + } + + public GuildCreationOptionsMsg(boolean close) { + super(Protocol.CHECKUNIQUEGUILD); + this.close = close; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildCreationOptionsMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHECKUNIQUEGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.screenType); + if (this.close) { + writer.putInt(2); + writer.putInt(0); +// writer.putInt(0); + return; + } + if (this.messageScreen != null) { + this.messageScreen._serialize(writer); + } else { + writer.putInt(0); + writer.putInt(5); + } + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.screenType = reader.getInt(); + if (this.screenType == 1) { + this.messageScreen = new Screen1(); + } else if (this.screenType == 2) { + this.messageScreen = new Screen2(); + } else { + System.out.println("Attempting to Deserilaize: Message Type" + screenType); + int counter = 0; + do { + int i = reader.getInt(); + System.out.println(counter++ + "->" + i); + } while (true); + } + this.messageScreen._deserialize(reader); + } + + /** + * @return the rulership + */ + public int getScreenType() { + return this.screenType; + } + + /** + * @param rulership the rulership to set + */ + public void setScreenType(int type) { + this.screenType = type; + } +} + +abstract class ScreenType { + public abstract void _serialize(ByteBufferWriter writer); + + public abstract void _deserialize(ByteBufferReader reader); +} + +class Screen1 extends ScreenType { + private int unknown01; + private int unknown02; + private String name; + private String motto; + + @Override + public void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putString(this.name); + } + + @Override + public void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.name = reader.getString(); + this.motto = reader.getString(); + } +} + +class Screen2 extends ScreenType { + private int unknown01; + private int unknown02; + private GuildTag gt; + + @Override + public void _serialize(ByteBufferWriter writer) { + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + GuildTag._serializeForGuildCreation(this.gt, writer); + } + + @Override + public void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.gt = new GuildTag(reader, true); + } +} + + diff --git a/src/engine/net/client/msg/guild/GuildInfoMsg.java b/src/engine/net/client/msg/guild/GuildInfoMsg.java new file mode 100644 index 00000000..aea3c464 --- /dev/null +++ b/src/engine/net/client/msg/guild/GuildInfoMsg.java @@ -0,0 +1,435 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.*; +import org.joda.time.DateTime; + + +public class GuildInfoMsg extends ClientNetMsg { + + private int msgType; + private int objectUUID; + private int objectType; + private Guild guild; + private AbstractGameObject ago; + + /** + * This is the general purpose constructor. + */ + public GuildInfoMsg() { + super(Protocol.UPDATEGUILD); + this.msgType = 4; + this.objectType = 0; + this.objectUUID = 0; + + } + + public GuildInfoMsg(AbstractGameObject ago, Guild guild, int unknown01) { + super(Protocol.UPDATEGUILD); + this.msgType = unknown01; + this.objectType = ago.getObjectType().ordinal(); + this.objectUUID = ago.getObjectUUID(); + this.ago = ago; + + this.guild = guild; + } + + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildInfoMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATEGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.msgType); + + if (this.msgType == 2) { + new GuildInfoMessageType2(this.ago, guild)._serialize(writer); + } else if (this.msgType == 5) { + new GuildInfoMessageType5(this.ago, guild)._serialize(writer); + } else if (this.msgType == 4) { + new GuildInfoMessageType4(this.ago, guild)._serialize(writer); + } else { + writer.putLong(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + } + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.msgType = reader.getInt(); + this.objectType = reader.getInt(); + this.objectUUID = reader.getInt(); + + if (this.msgType == 1) + reader.getInt(); + reader.getInt(); // PAdding + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.get(); + + //default guild tag deserializations. + if (this.msgType == 5) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + + reader.getInt(); + } + + + } + + /** + * @return the unknown01 + */ + public int getMsgType() { + return msgType; + } + + public void setMsgType(int msgType) { + this.msgType = msgType; + } + + /** + * @return the objectUUID + */ + public int getObjectID() { + return objectUUID; + } + + public int getObjectType() { + return objectType; + } + + public void setObjectType(int objectType) { + this.objectType = objectType; + } +} + +abstract class GuildInfoMessageType { + protected int objectType; + protected int objectID; + protected Guild g; + protected AbstractGameObject ago; + + public GuildInfoMessageType(AbstractGameObject ago, Guild g) { + this.objectType = ago.getObjectType().ordinal(); + this.objectID = ago.getObjectUUID(); + this.ago = ago; + this.g = g; + } + + abstract void _serialize(ByteBufferWriter writer); +} + +class GuildInfoMessageType2 extends GuildInfoMessageType { + + public GuildInfoMessageType2(AbstractGameObject ago, Guild g) { + super(ago, g); + } + + @Override + void _serialize(ByteBufferWriter writer) { + writer.putInt(this.objectType); + writer.putInt(this.objectID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(1); + + Guild nation = null; + if (this.g != null) { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(g.getObjectUUID()); + writer.putString(g.getName()); + + if (this.objectType == GameObjectType.PlayerCharacter.ordinal()) { + PlayerCharacter pc = PlayerCharacter.getFromCache(this.objectID); + + if (pc != null) { + writer.putInt(GuildStatusController.getRank(pc.getGuildStatus())); + } + } else { + writer.putInt(5); + } + GuildTag._serializeForDisplay(g.getGuildTag(), writer); + nation = this.g.getNation(); + } else { + writer.putLong(0L); + writer.putString(""); + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + } + + writer.putInt(1); + if (nation != null) { + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(nation.getObjectUUID()); + + City city = g.getOwnedCity(); + if (city != null) { + writer.putString(city.getCityName()); + writer.putInt(city.getObjectType().ordinal()); + writer.putInt(city.getObjectUUID()); + } else { + writer.putString(""); //city name + writer.putLong(0L); //should be city ID + } + + GuildTag._serializeForDisplay(nation.getGuildTag(), writer); + + } else { + writer.putLong(0L); + writer.putString(""); + writer.putLong(0L); + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + } + writer.putInt(0); + + writer.putInt(0); + writer.put((byte) 0); + } +} + +class GuildInfoMessageType4 extends GuildInfoMessageType { + + public GuildInfoMessageType4(AbstractGameObject ago, Guild g) { + super(ago, g); + } + + @Override + void _serialize(ByteBufferWriter writer) { + String cityName = ""; + writer.putInt(this.objectType); + writer.putInt(this.objectID); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + PlayerCharacter pc = PlayerCharacter.getFromCache(this.objectID); + if (this.g == null || pc == null) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + return; + } + + writer.putInt(1); + Guild nation = this.g.getNation(); + writer.putInt(0); + writer.putInt(0); + writer.putString(g.getName()); + writer.putInt(0); + GuildTag._serializeForDisplay(g.getGuildTag(), writer); + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + City city = g.getOwnedCity(); + if (city != null) + cityName = city.getCityName(); + writer.putString(nation.getName()); + writer.putInt(0); + writer.putInt(0); + + GuildTag._serializeForDisplay(nation.getGuildTag(), writer); + writer.putInt(1); + + writer.putString(g.getName()); + writer.putString(g.getMotto()); + writer.putString(nation.getName()); + writer.putInt(GuildStatusController.getRank(pc.getGuildStatus())); + writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus())); + writer.putInt(g.getCharter()); + writer.putString(cityName); //Shows City Name FUCK + AbstractCharacter guildLeader; + String guildLeaderName = ""; + if (g.isNPCGuild()) { + guildLeader = NPC.getFromCache(g.getGuildLeaderUUID()); + if (guildLeader != null) + guildLeaderName = guildLeader.getName(); + } else { + guildLeader = PlayerCharacter.getFromCache(g.getGuildLeaderUUID()); + if (guildLeader != null) + guildLeaderName = ((PlayerCharacter) guildLeader).getCombinedName(); + } + + writer.putString(guildLeaderName); + writer.putString(pc.getName()); + + writer.putDateTime(DateTime.now()); + writer.put((byte) 1); + writer.put((byte) 1); + writer.put((byte) 1); + writer.put((byte) 0); + writer.putInt(0); + + + // writer.put((byte)0); + + + // writer.putString(cityName); + // writer.putInt(10); + // writer.putInt(6); + // writer.putInt(10); + // writer.putString(cityName); + // writer.putString(pc.getFirstName()); + // writer.putString("Nation Leader"); + // writer.putDateTime(DateTime.now()); + // writer.put((byte)1); + // writer.put((byte)1); + // writer.putInt(1); + // writer.putInt(this.objectType); + // writer.putInt(this.objectID); + // writer.putInt(0); + // writer.putInt(0); + + + } +} + +class GuildInfoMessageType5 extends GuildInfoMessageType { + + public GuildInfoMessageType5(AbstractGameObject ago, Guild g) { + super(ago, g); + } + + @Override + void _serialize(ByteBufferWriter writer) { + + PlayerCharacter pc = null; + + if (ago.getObjectType().equals(GameObjectType.PlayerCharacter)) { + pc = (PlayerCharacter) ago; + } + + if (pc != null && g != null && g.getObjectUUID() != 0) { + Guild n = g.getNation(); + if (n == null) { + n = Guild.getErrantNation(); + } + + writer.putInt(ago.getObjectType().ordinal()); + writer.putInt(ago.getObjectUUID()); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + + writer.putString(g.getName()); //No Effect? + writer.putInt(0); //Pad + + GuildTag._serializeForDisplay(g.getGuildTag(), writer); //Also a waste of space... + + writer.putInt(1); + writer.putInt(0); + writer.putInt(0); + writer.putString(n.getName()); //No Effect? + writer.putInt(0); + writer.putInt(0); + + GuildTag._serializeForDisplay(n.getGuildTag(), writer); + + writer.putInt(1); + + writer.putString(g.getName()); //Guild Name + writer.putString(g.getMotto()); //TODO Motto + writer.putString(n.getName()); //Nation Name + + writer.putInt(GuildStatusController.getRank(pc.getGuildStatus())); //Rank + writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus())); //Title + writer.putInt(g.getCharter()); + + if (g.getNation().equals(Guild.getErrantNation())) + writer.putString("Errant"); + else + writer.putString("City"); + writer.putString("Guild Leader"); + writer.putString(""); //Nation Leader - I believe + + DateTime now = DateTime.now(); + writer.putDateTime(now); + + writer.put((byte) 1); + writer.put((byte) 1); + writer.put((byte) 0); + writer.put((byte) 0); + + GuildTag._serializeForDisplay(g.getGuildTag(), writer); + GuildTag._serializeForDisplay(g.getNation().getGuildTag(), writer); + writer.putInt(0); + } else { + writer.putLong(0); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.put((byte) 0); + + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + GuildTag._serializeForDisplay(GuildTag.ERRANT, writer); + + writer.putInt(0); + } + } +} diff --git a/src/engine/net/client/msg/guild/GuildListMsg.java b/src/engine/net/client/msg/guild/GuildListMsg.java new file mode 100644 index 00000000..f40783be --- /dev/null +++ b/src/engine/net/client/msg/guild/GuildListMsg.java @@ -0,0 +1,228 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.gameManager.SessionManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Guild; +import engine.objects.GuildHistory; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; + +import java.util.ArrayList; + + +public class GuildListMsg extends ClientNetMsg { + + private GuildListMessageType glm; + + /** + * Type 1 Constructor - Guild Roster + */ + public GuildListMsg(Guild g) { + super(Protocol.SENDMEMBERENTRY); + this.glm = new GuildListMessageType1(g); + } + + /** + * Type 4 Constructor - Guild History + */ + public GuildListMsg(PlayerCharacter pc) { + super(Protocol.SENDMEMBERENTRY); + this.glm = new GuildListMessageType2(pc); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GuildListMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SENDMEMBERENTRY, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + //TODO Find Default and null check this + this.glm._serialize(writer); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + /* + * + * The Server should never receive this message directly. + * Instances in recordings will need to be decoded by hand, + * converting from our format to the standard format will + * cause more problems than its worth to fix. + * + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.get(); + this.unknown04 = reader.getInt(); + this.unknown05 = reader.get(); + this.unknown06 = reader.getInt(); + int size = reader.getInt(); + for (int i = 0; i < size; i++) { + GuildTableList gt = new GuildTableList(); + reader.getInt(); // Player Character ID Type + gt.setUUID(reader.getInt()); + gt.setName(reader.getString()); + gt.setActionType(reader.get()); + gt.setGuildTitle(reader.getInt()); + gt.setGuildRank(reader.getInt()); + gt.setUnknown02(reader.getInt()); + gt.setUnknown03(reader.getInt()); + gt.setUnknown04(reader.getInt()); + gt.setUnknown05(reader.getInt()); + gt.setUnknown06(reader.getInt()); + gt.setUnknown07(reader.getInt()); + } + */ + } + + @Override + protected int getPowerOfTwoBufferSize() { + // Larger size for historically larger opcodes + return 15; + } +} + +abstract class GuildListMessageType { + public ArrayList history = new ArrayList<>(); + + abstract void _serialize(ByteBufferWriter writer); +} + +class GuildListMessageType1 extends GuildListMessageType { + + private Guild g; + + public GuildListMessageType1(Guild g) { + this.g = g; + } + + @Override + void _serialize(ByteBufferWriter writer) { + Enum.GuildType gt = Enum.GuildType.getGuildTypeFromInt(g.getCharter()); + + writer.putInt(1); + writer.putInt(gt.ordinal()); //Charter Type + writer.putInt(1); //Always 1? + writer.put((byte) 1); + writer.put((byte) 0); + writer.putInt(gt.getNumberOfRanks()); //Number of Ranks + + + ArrayList guildRoster = Guild.GuildRoster(g); + writer.putInt(guildRoster.size() + g.getBanishList().size()); + + // Send guild list of each player + for (PlayerCharacter player : guildRoster) { + + byte isActive = SessionManager.getPlayerCharacterByID(player.getObjectUUID()) != null ? (byte) 1 : (byte) 0; + writer.putInt(Enum.GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(player.getObjectUUID()); + writer.putString(player.getCombinedName()); + writer.put(isActive); //Active? + writer.putInt(GuildStatusController.getTitle(player.getGuildStatus())); + writer.putInt(GuildStatusController.getRank(player.getGuildStatus())); + writer.putInt(0); // 1/2 has no effect + writer.putInt(0); // 1 has no effect + writer.putInt(0); // 1 has no effect + writer.putInt(0); // 1 has no effect + writer.putInt(GuildStatusController.getRank(player.getGuildStatus())); + writer.putInt(0); // window failed to open when set to 1 + } + + for (PlayerCharacter banished : g.getBanishList()) { + byte isActive = SessionManager.getPlayerCharacterByID(banished.getObjectUUID()) != null ? (byte) 1 : (byte) 0; + writer.putInt(Enum.GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(banished.getObjectUUID()); + writer.putString(banished.getCombinedName()); + writer.put(isActive); //Active? + writer.putInt(GuildStatusController.getTitle(banished.getGuildStatus())); + writer.putInt(3); + writer.putInt(0); // 1/2 has no effect + writer.putInt(0); // 1 has no effect + writer.putInt(0); // 1 has no effect + writer.putInt(0); // 1 has no effect + writer.putInt(3); + writer.putInt(0); // window failed to open when set to 1 + } + } +} + +class GuildListMessageType2 extends GuildListMessageType { + + private PlayerCharacter pc; + + public GuildListMessageType2(PlayerCharacter pc) { + this.pc = pc; + } + + @Override + void _serialize(ByteBufferWriter writer) { + + Guild g = pc.getGuild(); + + writer.putInt(4); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.put((byte) 0); + writer.put((byte) 0); + writer.putInt(1); + writer.putInt(pc.getObjectType().ordinal()); + writer.putInt(pc.getObjectUUID()); + writer.putString(pc.getCombinedName()); + + writer.put((byte) 1); + writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus())); //Title Maybe? + writer.putInt(GuildStatusController.getRank(pc.getGuildStatus())); //Rank? + + writer.putInt(pc.getRaceToken()); //race token + writer.putInt(pc.getBaseClassToken()); //class token + + writer.putInt(2); //PAD + writer.putInt(pc.getLevel()); + writer.putInt(g.getCharter()); + + //TODO Get Guild History from the DB + //ArrayList history = DbManager.GuildQueries.GET_GUILD_HISTORY_OF_PLAYER((int)pc.getPlayerUUID()); + writer.putInt(pc.getGuildHistory().size()); //Number of Entries + + for (GuildHistory guildHistory : pc.getGuildHistory()) { + writer.putInt(guildHistory.getHistoryType().getType()); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt((int) guildHistory.getGuildID()); + writer.putString(guildHistory.getGuildName()); + writer.putInt(0); + writer.putDateTime(guildHistory.getTime()); + } + } + +} diff --git a/src/engine/net/client/msg/guild/GuildUnknownMsg.java b/src/engine/net/client/msg/guild/GuildUnknownMsg.java new file mode 100644 index 00000000..87ff4020 --- /dev/null +++ b/src/engine/net/client/msg/guild/GuildUnknownMsg.java @@ -0,0 +1,66 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class GuildUnknownMsg extends ClientNetMsg { + + private int unknown01; + private int unknown02; + private int unknown03; + private byte unknownByte; + + + /** + * This is the general purpose constructor. + */ + public GuildUnknownMsg() { + super(Protocol.UPDATECLIENTALLIANCES); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public GuildUnknownMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.UPDATECLIENTALLIANCES, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); +// writer.putInt(this.unknown01); +// writer.putInt(this.unknown02); +// writer.putInt(this.unknown02); +// writer.putInt(this.unknownByte); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.unknown01 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknownByte = reader.get(); + } +} diff --git a/src/engine/net/client/msg/guild/InviteToGuildMsg.java b/src/engine/net/client/msg/guild/InviteToGuildMsg.java new file mode 100644 index 00000000..48d9369f --- /dev/null +++ b/src/engine/net/client/msg/guild/InviteToGuildMsg.java @@ -0,0 +1,190 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.GuildTag; + +public class InviteToGuildMsg extends ClientNetMsg { + + private int response; + private int sourceType; + private int sourceUUID; + private int targetType; + private int targetUUID; + private int unknown01; + private String message; + private GuildTag gt; + private String guildName; + private int guildType; + private int guildID; + private String targetName; + + /** + * This is the general purpose constructor. + */ + public InviteToGuildMsg() { + super(Protocol.INVITETOGUILD); + this.response = 0; + this.message = "No error message"; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public InviteToGuildMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.INVITETOGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.response); + writer.putLong(this.sourceUUID); + writer.putLong(this.targetUUID); + writer.putInt(this.unknown01); + writer.putString(this.message); + writer.putInt(this.gt.backgroundColor01); + writer.putInt(this.gt.backgroundColor02); + writer.putInt(this.gt.symbolColor); + writer.putInt(this.gt.symbol); + writer.putInt(this.gt.backgroundDesign); + writer.putString(this.guildName); + writer.putInt(this.guildType); + writer.putInt(this.guildID); + writer.putString(this.targetName); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.response = reader.getInt(); + this.sourceType = reader.getInt(); + this.sourceUUID = reader.getInt(); + this.targetType = reader.getInt(); + this.targetUUID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + this.gt = new GuildTag(reader); + this.guildName = reader.getString(); + this.guildType = reader.getInt(); + this.guildID = reader.getInt(); + this.targetName = reader.getString(); + } + + public GuildTag getGuildTag() { + return this.gt; + } + + public void setGuildTag(GuildTag gt) { + this.gt = gt; + } + + /** + * @return the response + */ + public int getResponse() { + return response; + } + + /** + * @param response the response to set + */ + public void setResponse(int response) { + this.response = response; + } + + /** + * @return the sourceUUID + */ + public int getSourceUUID() { + return sourceUUID; + } + + /** + * @param sourceUUID the sourceUUID to set + */ + public void setSourceUUID(int sourceUUID) { + this.sourceUUID = sourceUUID; + } + + /** + * @return the targetUUID + */ + public int getTargetUUID() { + return targetUUID; + } + + /** + * @param targetUUID the targetUUID to set + */ + public void setTargetUUID(int targetUUID) { + this.targetUUID = targetUUID; + } + + /** + * @param guildName the guildName to set + */ + public void setGuildName(String guildName) { + this.guildName = guildName; + } + + /** + * @param guildID the guildID to set + */ + public void setGuildUUID(int guildID) { + this.guildID = guildID; + } + + /** + * @return the targetName + */ + public String getTargetName() { + return targetName; + } + + /** + * @param targetName the targetName to set + */ + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public void setSourceType(int sourceType) { + this.sourceType = sourceType; + } + + public void setGuildType(int guildType) { + this.guildType = guildType; + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + public void setTargetType(int targetType) { + this.targetType = targetType; + } +} diff --git a/src/engine/net/client/msg/guild/InviteToSubMsg.java b/src/engine/net/client/msg/guild/InviteToSubMsg.java new file mode 100644 index 00000000..4fa7b078 --- /dev/null +++ b/src/engine/net/client/msg/guild/InviteToSubMsg.java @@ -0,0 +1,198 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.GuildTag; + +public class InviteToSubMsg extends ClientNetMsg { + + //12d67402 0000f062 56253600 0010b062 d7821800 00000000 + //10000000 4e006f0020006500720072006f00720020006d00650073007300610067006500 + //0a000000 03000000 07000000 a0000000 04000000 + //17000000 4800650072006f006500730020006f0066002000530065006100200044006f006700730020005200650073007400 + //0000200a 33000000 00000000 01000000 + + //12d67402 0000d062 11fe1700 00009063 3ee91300 00000000 + //10000000 4e006f0020006500720072006f00720020006d00650073007300610067006500 + //08000000 08000000 07000000 96000000 0d000000 + //11000000 42006c006f006f00640020004d006f006f006e00200052006900730069006e006700 + //0000c006 64540000 01000000 00000000 + + //12d67402 0000e062 db2d0000 00005063 116e1100 00000000 + //10000000 4e006f0020006500720072006f00720020006d00650073007300610067006500 + //05000000 11000000 0e000000 95000000 05000000 + //09000000 530074006f0072006d0068006f006c006400 + //0000c006 56300000 01000000 00000000 + + private int sourceUUID; + private int targetUUID; + private int unknown01; + private String message; + private GuildTag gt; + private String guildName; + private int guildUUID; + private int unknown02; + private int unknown03; + + /** + * This is the general purpose constructor. + */ + public InviteToSubMsg() { + super(Protocol.INVITEGUILDFEALTY); + this.message = "No error message"; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public InviteToSubMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.INVITEGUILDFEALTY, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.sourceUUID); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.targetUUID); + writer.putInt(this.unknown01); + writer.putString(this.message); + writer.putInt(this.gt.backgroundColor01); + writer.putInt(this.gt.backgroundColor02); + writer.putInt(this.gt.symbolColor); + writer.putInt(this.gt.symbol); + writer.putInt(this.gt.backgroundDesign); + writer.putString(this.guildName); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.guildUUID); + writer.putInt(this.unknown02); + writer.putInt(this.unknown03); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + this.sourceUUID = reader.getInt(); + reader.getInt(); + this.targetUUID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + this.gt = new GuildTag(reader); + this.guildName = reader.getString(); + reader.getInt(); // Padding for Object Type + this.guildUUID = reader.getInt(); + this.unknown02 = reader.getInt(); + this.unknown03 = reader.getInt(); + } + + public GuildTag getGuildTag() { + return this.gt; + } + + public void setGuildTag(GuildTag gt) { + this.gt = gt; + } + + /** + * @return the sourceUUID + */ + public int getSourceUUID() { + return sourceUUID; + } + + /** + * @return the targetUUID + */ + public int getTargetUUID() { + return targetUUID; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * @return the guildName + */ + public String getGuildName() { + return guildName; + } + + /** + * @param guildName the guildName to set + */ + public void setGuildName(String guildName) { + this.guildName = guildName; + } + + + public int getUnknown02() { + return unknown02; + } + + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + public int getUnknown03() { + return unknown03; + } + + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + public int getGuildUUID() { + return guildUUID; + } + + public void setGuildUUID(int guildUUID) { + this.guildUUID = guildUUID; + } +} diff --git a/src/engine/net/client/msg/guild/LeaveGuildMsg.java b/src/engine/net/client/msg/guild/LeaveGuildMsg.java new file mode 100644 index 00000000..f8a56216 --- /dev/null +++ b/src/engine/net/client/msg/guild/LeaveGuildMsg.java @@ -0,0 +1,101 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class LeaveGuildMsg extends ClientNetMsg { + + private int response; + private String message; + private long sourceID; + + /** + * This is the general purpose constructor. + */ + public LeaveGuildMsg() { + super(Protocol.LEAVEGUILD); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public LeaveGuildMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LEAVEGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.response); + writer.putString(this.message); + writer.putLong(this.sourceID); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.response = reader.getInt(); + this.message = reader.getString(); + this.sourceID = reader.getLong(); + } + + /** + * @return the response + */ + public int getResponse() { + return response; + } + + /** + * @param response the response to set + */ + public void setResponse(int response) { + this.response = response; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * @return the sourceID + */ + public long getSourceID() { + return sourceID; + } + + /** + * @param sourceID the sourceID to set + */ + public void setSourceID(long sourceID) { + this.sourceID = sourceID; + } + +} diff --git a/src/engine/net/client/msg/guild/MOTDCommitMsg.java b/src/engine/net/client/msg/guild/MOTDCommitMsg.java new file mode 100644 index 00000000..6f821824 --- /dev/null +++ b/src/engine/net/client/msg/guild/MOTDCommitMsg.java @@ -0,0 +1,107 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class MOTDCommitMsg extends ClientNetMsg { + + private String message; + private int unknown01; + private int type; + + /** + * This is the general purpose constructor. + */ + public MOTDCommitMsg() { + super(Protocol.SETMOTD); + this.message = ""; + this.unknown01 = 0; + this.type = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public MOTDCommitMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SETMOTD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.message); + writer.putInt(this.unknown01); + writer.putInt(this.type); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.message = reader.getString(); + this.unknown01 = reader.getInt(); + this.type = reader.getInt(); + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(int type) { + this.type = type; + } + +} diff --git a/src/engine/net/client/msg/guild/MOTDMsg.java b/src/engine/net/client/msg/guild/MOTDMsg.java new file mode 100644 index 00000000..16060ae5 --- /dev/null +++ b/src/engine/net/client/msg/guild/MOTDMsg.java @@ -0,0 +1,129 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class MOTDMsg extends ClientNetMsg { + + private int type; + private byte response; + private int unknown01; + private String message; + + /** + * This is the general purpose constructor. + */ + public MOTDMsg() { + super(Protocol.MOTD); + this.type = 0; + this.response = (byte) 0; + this.unknown01 = 0; + this.message = ""; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public MOTDMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.MOTD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.type); + writer.put(this.response); + if (this.response == (byte) 1) { + writer.putInt(this.unknown01); + writer.putString(this.message); + } + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.type = reader.getInt(); + this.response = reader.get(); + if (this.response == (byte) 1) { + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + } + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(int type) { + this.type = type; + } + + /** + * @return the response + */ + public byte getResponse() { + return response; + } + + /** + * @param response the response to set + */ + public void setResponse(byte response) { + this.response = response; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/engine/net/client/msg/guild/ReqGuildListMsg.java b/src/engine/net/client/msg/guild/ReqGuildListMsg.java new file mode 100644 index 00000000..8dbe382a --- /dev/null +++ b/src/engine/net/client/msg/guild/ReqGuildListMsg.java @@ -0,0 +1,55 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class ReqGuildListMsg extends ClientNetMsg { + + private long nationCompID; + private long guildToSubCompID; + + /** + * This is the general purpose constructor. + */ + public ReqGuildListMsg() { + super(Protocol.REQUESTGUILDLIST); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public ReqGuildListMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REQUESTGUILDLIST, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); + reader.getString(); + } + +} diff --git a/src/engine/net/client/msg/guild/SendGuildEntryMsg.java b/src/engine/net/client/msg/guild/SendGuildEntryMsg.java new file mode 100644 index 00000000..6398c14c --- /dev/null +++ b/src/engine/net/client/msg/guild/SendGuildEntryMsg.java @@ -0,0 +1,105 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Guild; +import engine.objects.GuildTag; +import engine.objects.PlayerCharacter; + +import java.util.ArrayList; + +public class SendGuildEntryMsg extends ClientNetMsg { + + private PlayerCharacter pc; + + /** + * This is the general purpose constructor. + */ + public SendGuildEntryMsg(PlayerCharacter pc) { + super(Protocol.SENDGUILDENTRY); + this.pc = pc; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public SendGuildEntryMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SENDGUILDENTRY, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + ArrayList subsAndSovs = new ArrayList<>(); + + Guild nation = pc.getGuild().getNation(); + if (pc.getGuild() != nation) + subsAndSovs.add(nation); + subsAndSovs.addAll(pc.getGuild().getSubGuildList()); + + + writer.putInt(1); + //GuildState + //Petitioner = 2, Sworn = 4 , Nation = 5, protectorate = 6, city-State = 7(nation), province = 8, + + writer.putInt(subsAndSovs.size()); + writer.putInt(1); + if (subsAndSovs.size() > 0) { + + for (Guild guild : subsAndSovs) { + int state = guild.getGuildState().getStateID(); + + writer.putInt(guild.getObjectType().ordinal()); + writer.putInt(guild.getObjectUUID()); + + writer.putString(guild.getName()); + + //TODO set Alliance date + writer.putShort((short) 1); + writer.putInt(0); + writer.putShort((short) 0); + writer.put((byte) 0); + + writer.putInt(state); + GuildTag._serializeForDisplay(guild.getGuildTag(), writer); + if (guild == nation) + writer.putInt(2); // Break Fealty + else + writer.putInt(1); // Dismiss, Swear In. + } + } + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + } + + public PlayerCharacter getPc() { + return pc; + } + + public void setPc(PlayerCharacter pc) { + this.pc = pc; + } + +} diff --git a/src/engine/net/client/msg/guild/SwearInGuildMsg.java b/src/engine/net/client/msg/guild/SwearInGuildMsg.java new file mode 100644 index 00000000..a9051f59 --- /dev/null +++ b/src/engine/net/client/msg/guild/SwearInGuildMsg.java @@ -0,0 +1,77 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class SwearInGuildMsg extends ClientNetMsg { + + private int guildType; + private int guildUUID; + + private String message; + + /** + * This is the general purpose constructor. + */ + public SwearInGuildMsg() { + super(Protocol.SWEARINGUILD); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public SwearInGuildMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SWEARINGUILD, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + + this.guildType = reader.getInt(); + this.guildUUID = reader.getInt(); + reader.getInt(); + this.message = reader.getString(); + } + + /** + * @return the targetType + */ + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + + public int getGuildUUID() { + return guildUUID; + } + +} diff --git a/src/engine/net/client/msg/guild/SwearInMsg.java b/src/engine/net/client/msg/guild/SwearInMsg.java new file mode 100644 index 00000000..aa9c239e --- /dev/null +++ b/src/engine/net/client/msg/guild/SwearInMsg.java @@ -0,0 +1,118 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.guild; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class SwearInMsg extends ClientNetMsg { + + private int targetType; + private int targetID; + private int unknown01; + private String message; + + /** + * This is the general purpose constructor. + */ + public SwearInMsg() { + super(Protocol.ACTIVATEPLEDGE); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public SwearInMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ACTIVATEPLEDGE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.targetType); + writer.putInt(this.targetID); + writer.putInt(this.unknown01); + writer.putString(this.message); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.targetType = reader.getInt(); + this.targetID = reader.getInt(); + this.unknown01 = reader.getInt(); + this.message = reader.getString(); + } + + /** + * @return the targetType + */ + public int getTargetType() { + return targetType; + } + + /** + * @param targetType the targetType to set + */ + public void setTargetType(int targetType) { + this.targetType = targetType; + } + + /** + * @return the targetID + */ + public int getTargetID() { + return targetID; + } + + /** + * @param targetID the targetID to set + */ + public void setTargetID(int targetID) { + this.targetID = targetID; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/engine/net/client/msg/login/CharSelectScreenMsg.java b/src/engine/net/client/msg/login/CharSelectScreenMsg.java new file mode 100644 index 00000000..34fd3f32 --- /dev/null +++ b/src/engine/net/client/msg/login/CharSelectScreenMsg.java @@ -0,0 +1,287 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.exception.SerializationException; +import engine.net.AbstractConnection; +import engine.net.AbstractNetMsg; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.objects.Account; +import engine.objects.PlayerCharacter; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + + +public class CharSelectScreenMsg extends ClientNetMsg { + + private int numChars; + private int selectedIndex; + private int static01; + private byte static02; + private ArrayList chars; + private boolean fromCommit; + private Account account; + + /** + * Special Constructor + * + * @param s + */ + public CharSelectScreenMsg(Session s) { + this(s, false); + } + + /** + * Special Constructor + * + * @param s + * @param fromCommit + */ + public CharSelectScreenMsg(Session s, boolean fromCommit) { + super(Protocol.CHARSELECTSCREEN); + this.fromCommit = fromCommit; + this.chars = new ArrayList<>(); + // get this account + this.account = s.getAccount(); + + // Get all the character records for this account + chars = new ArrayList<>(this.account.characterMap.values()); + + if (chars == null) { + this.chars = new ArrayList<>(); + } + + // idiot check the quantity of the ArrayList/numChars + this.numChars = chars.size(); + if (this.numChars > 7) { + Logger.error("Account '" + this.account.getUname() + "' has more than 7 characters."); + + this.numChars = 7; + } + + // Get the last character used (As a composite ID). + int lastChar = s.getAccount().getLastCharIDUsed(); + + // Look it up for the index # + this.selectedIndex = 0; + + for (PlayerCharacter pc : chars) + if (pc.getObjectUUID() == lastChar) + break; + else + selectedIndex++; + + // idiot check the index # + if (this.selectedIndex < 0) { + this.selectedIndex = 0; + } + if (this.selectedIndex > 6) { + this.selectedIndex = 6; + } + + this.static01 = 7; + this.static02 = (byte) 1; + } + + /** + * This is the general purpose constructor. + */ + public CharSelectScreenMsg() { + super(Protocol.CHARSELECTSCREEN); + this.chars = new ArrayList<>(); + this.selectedIndex = 0; + this.static01 = 7; + this.static02 = (byte) 1; + this.fromCommit = false; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public CharSelectScreenMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CHARSELECTSCREEN, origin, reader); + + this.chars = new ArrayList<>(); + } + + /** + * @see AbstractNetMsg#getPowerOfTwoBufferSize() + */ + @Override + protected int getPowerOfTwoBufferSize() { + //Larger size for historically larger opcodes + return (17); // 2^17 == 131,072 + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + if (this.account == null) + Logger.error("failed to find account for message"); + + // Double check char belongs to this account + for (int i = 0; i < this.numChars; ++i) { + if (this.chars.get(i) == null) + Logger.error("failed to find character"); + if (this.chars.get(i).getAccount() == null) + Logger.error("failed to find account for character " + + this.chars.get(i).getObjectUUID()); + if (this.chars.get(i).getAccount().getObjectUUID() != this.account.getObjectUUID()) { + this.chars.remove(i); + this.numChars--; + + Logger.error("This character doesn't belong to this account."); + + } + } + + writer.putInt(this.numChars); // 4bytes + writer.putInt(this.selectedIndex); // 4bytes + writer.putInt(this.static01); // 4bytes + writer.put(this.static02); // 1 byte + + for (int i = 0; i < this.numChars; ++i) { + try { + if (!fromCommit) + PlayerCharacter.serializeForClientMsgLogin(this.chars.get(i), writer); + else + PlayerCharacter.serializeForClientMsgCommit(this.chars.get(i), writer); + } catch (SerializationException e) { + Logger.error("failed to serialize character " + this.chars.get(i).getObjectUUID()); + // Handled already. + } + } + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.numChars = reader.getInt(); + this.selectedIndex = reader.getInt(); + + this.static01 = reader.monitorInt(0, "CharSelectScreenMsg-01"); + this.static02 = reader.monitorByte((byte) 0, "CharSelectScreenMsg-02"); + + // TODO is this correct?!?!? + } + + /** + * @return the numChars + */ + public int getNumChars() { + return numChars; + } + + /** + * @param numChars the numChars to set + */ + public void setNumChars(int numChars) { + this.numChars = numChars; + } + + /** + * @return the selectedIndex + */ + public int getSelectedIndex() { + return selectedIndex; + } + + /** + * @param selectedIndex the selectedIndex to set + */ + public void setSelectedIndex(int selectedIndex) { + this.selectedIndex = selectedIndex; + } + + /** + * @return the static01 + */ + public int getStatic01() { + return static01; + } + + /** + * @param static01 the static01 to set + */ + public void setStatic01(int static01) { + this.static01 = static01; + } + + /** + * @return the static02 + */ + public byte getStatic02() { + return static02; + } + + /** + * @param static02 the static02 to set + */ + public void setStatic02(byte static02) { + this.static02 = static02; + } + + /** + * @return the chars + */ + public ArrayList getChars() { + return chars; + } + + /** + * @param chars the chars to set + */ + public void setChars(ArrayList chars) { + this.chars = chars; + } + + /** + * @return the fromCommit + */ + public boolean isFromCommit() { + return fromCommit; + } + + /** + * @param fromCommit the fromCommit to set + */ + public void setFromCommit(boolean fromCommit) { + this.fromCommit = fromCommit; + } + + /** + * @return the account + */ + public Account getAccount() { + return account; + } + + /** + * @param account the account to set + */ + public void setAccount(Account account) { + this.account = account; + } + +} diff --git a/src/engine/net/client/msg/login/ClientLoginInfoMsg.java b/src/engine/net/client/msg/login/ClientLoginInfoMsg.java new file mode 100644 index 00000000..419360f5 --- /dev/null +++ b/src/engine/net/client/msg/login/ClientLoginInfoMsg.java @@ -0,0 +1,257 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class ClientLoginInfoMsg extends ClientNetMsg { + + private String uname; + private String pword; + + private int unknown01; + private int unknown02; + + private String os; + + private int unknown03; + private int unknown04; + private int unknown05; + private int unknown06; + private int unknown07; + private int unknown08; + private short unknown09; + + /** + * This is the general purpose constructor. + */ + public ClientLoginInfoMsg(String uName, String pWord, String os) { + super(Protocol.LOGIN); + this.uname = uName; + this.pword = pWord; + this.os = os; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ClientLoginInfoMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LOGIN, origin, reader); + } + + /** + * This is the Copy constructor. + */ + public ClientLoginInfoMsg(ClientLoginInfoMsg msg) { + super(Protocol.LOGIN, msg); + this.uname = msg.uname; + this.pword = msg.pword; + this.os = msg.os; + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.uname); + writer.putString(this.pword); + writer.putInt(this.unknown01); + writer.putInt(this.unknown02); + writer.putString(this.os); + writer.putInt(this.unknown03); + writer.putInt(this.unknown04); + writer.putInt(this.unknown05); + writer.putInt(this.unknown06); + writer.putInt(this.unknown07); + writer.putInt(this.unknown08); + writer.putShort(this.unknown09); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.uname = reader.getString(); + this.pword = reader.getString(); + + this.unknown01 = reader.monitorInt(0, "ClientLoginInfoMsg 01"); + this.unknown02 = reader.monitorInt(0, "ClientLoginInfoMsg 02"); + + this.os = reader.getString(); + + this.unknown03 = reader.monitorInt(0, "ClientLoginInfoMsg 03"); + this.unknown04 = reader.monitorInt(0, "ClientLoginInfoMsg 04"); + this.unknown05 = reader.monitorInt(0, "ClientLoginInfoMsg 05"); + this.unknown06 = reader.monitorInt(0, "ClientLoginInfoMsg 06"); + this.unknown07 = reader.monitorInt(0, "ClientLoginInfoMsg 07"); + this.unknown08 = reader.monitorInt(0, "ClientLoginInfoMsg 08"); + this.unknown09 = reader + .monitorShort((short) 0, "ClientLoginInfoMsg 09"); + + } + + /** + * @return the uname + */ + public String getUname() { + return uname; + } + + /** + * @return the pword + */ + public String getPword() { + return pword; + } + + /** + * @return the os + */ + public String getOs() { + return os; + } + + /** + * @return the unknown01 + */ + public int getUnknown01() { + return unknown01; + } + + /** + * @param unknown01 the unknown01 to set + */ + public void setUnknown01(int unknown01) { + this.unknown01 = unknown01; + } + + /** + * @return the unknown02 + */ + public int getUnknown02() { + return unknown02; + } + + /** + * @param unknown02 the unknown02 to set + */ + public void setUnknown02(int unknown02) { + this.unknown02 = unknown02; + } + + /** + * @return the unknown03 + */ + public int getUnknown03() { + return unknown03; + } + + /** + * @param unknown03 the unknown03 to set + */ + public void setUnknown03(int unknown03) { + this.unknown03 = unknown03; + } + + /** + * @return the unknown04 + */ + public int getUnknown04() { + return unknown04; + } + + /** + * @param unknown04 the unknown04 to set + */ + public void setUnknown04(int unknown04) { + this.unknown04 = unknown04; + } + + /** + * @return the unknown05 + */ + public int getUnknown05() { + return unknown05; + } + + /** + * @param unknown05 the unknown05 to set + */ + public void setUnknown05(int unknown05) { + this.unknown05 = unknown05; + } + + /** + * @return the unknown06 + */ + public int getUnknown06() { + return unknown06; + } + + /** + * @param unknown06 the unknown06 to set + */ + public void setUnknown06(int unknown06) { + this.unknown06 = unknown06; + } + + /** + * @return the unknown07 + */ + public int getUnknown07() { + return unknown07; + } + + /** + * @param unknown07 the unknown07 to set + */ + public void setUnknown07(int unknown07) { + this.unknown07 = unknown07; + } + + /** + * @return the unknown08 + */ + public int getUnknown08() { + return unknown08; + } + + /** + * @param unknown08 the unknown08 to set + */ + public void setUnknown08(int unknown08) { + this.unknown08 = unknown08; + } + + /** + * @return the unknown09 + */ + public short getUnknown09() { + return unknown09; + } + + /** + * @param unknown09 the unknown09 to set + */ + public void setUnknown09(short unknown09) { + this.unknown09 = unknown09; + } + +} diff --git a/src/engine/net/client/msg/login/CommitNewCharacterMsg.java b/src/engine/net/client/msg/login/CommitNewCharacterMsg.java new file mode 100644 index 00000000..673656cf --- /dev/null +++ b/src/engine/net/client/msg/login/CommitNewCharacterMsg.java @@ -0,0 +1,428 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class CommitNewCharacterMsg extends ClientNetMsg { + + private String firstName; + private String lastName; + private int serverID; + private int hairStyle; + private int beardStyle; + private int skinColor; + private int hairColor; + private int beardColor; + private int kit; + private int numRunes; + private int[] runes; + private int numStats; + private int strengthMod; + private int dexterityMod; + private int constitutionMod; + private int intelligenceMod; + private int spiritMod; + + /** + * This is the general purpose constructor. + */ + public CommitNewCharacterMsg() { + super(Protocol.CREATECHAR); + runes = new int[23]; + strengthMod = 0; + dexterityMod = 0; + constitutionMod = 0; + intelligenceMod = 0; + spiritMod = 0; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public CommitNewCharacterMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.CREATECHAR, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + + writer.putString(this.firstName); + writer.putString(this.lastName); + + writer.putInt(this.serverID); + writer.putInt(0); + writer.putInt(this.hairStyle); + writer.putInt(0); + writer.putInt(this.beardStyle); + writer.putInt(this.skinColor); + writer.putInt(this.hairColor); + writer.putInt(this.beardColor); + writer.putInt(this.kit); + for (int i = 0; i < 23; i++) { + writer.putInt(0); + writer.putInt(this.runes[i]); + writer.putInt(0); + writer.putInt(0); + } + writer.putInt(this.numStats); + if (this.strengthMod != 0) { + writer.putInt(0x8AC3C0E6); + writer.putInt(this.strengthMod); + } + if (this.dexterityMod != 0) { + writer.putInt(0xE07B3336); + writer.putInt(this.dexterityMod); + } + if (this.constitutionMod != 0) { + writer.putInt(0xB15DC77E); + writer.putInt(this.constitutionMod); + } + if (this.intelligenceMod != 0) { + writer.putInt(0xFF665EC3); + writer.putInt(this.intelligenceMod); + } + if (this.spiritMod != 0) { + writer.putInt(0xACB82E33); + writer.putInt(this.spiritMod); + } + + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + runes = new int[23]; + runes = new int[23]; + strengthMod = 0; + dexterityMod = 0; + constitutionMod = 0; + intelligenceMod = 0; + spiritMod = 0; + + this.firstName = reader.getString(); + this.lastName = reader.getString(); + this.serverID = reader.getInt(); + + reader.monitorInt(0, "CommitNewCharacter 01"); + + this.hairStyle = reader.getInt(); + + reader.monitorInt(0, "CommitNewCharacter 02"); + + this.beardStyle = reader.getInt(); + this.skinColor = reader.getInt(); + this.hairColor = reader.getInt(); + this.beardColor = reader.getInt(); + this.kit = reader.getInt(); + this.clearRunes(); + int runeCount = 0; + for (int i = 0; i < 23; i++) { + + reader.monitorInt(0, "CommitNewCharacter 03-" + i); + + this.runes[i] = reader.getInt(); + + reader.monitorInt(0, "CommitNewCharacter 04-" + i); + reader.monitorInt(0, "CommitNewCharacter 05-" + i); + + if (this.runes[i] != 0) + runeCount++; + } + this.numRunes = runeCount; + this.numStats = reader.getInt(); + int stattype; + for (int i = 0; i < this.numStats; i++) { + stattype = reader.getInt(); + if (stattype == 0x8AC3C0E6) + this.strengthMod = reader.getInt(); + else if (stattype == 0xE07B3336) + this.dexterityMod = reader.getInt(); + else if (stattype == 0xB15DC77E) + this.constitutionMod = reader.getInt(); + else if (stattype == 0xFF665EC3) + this.intelligenceMod = reader.getInt(); + else if (stattype == 0xACB82E33) + this.spiritMod = reader.getInt(); + } + } + + + public void clearRunes() { + for (int i = 0; i < 23; i++) + this.runes[i] = 0; + } + + public int getRace() { + for (int i = 0; i < 23; i++) + if (this.runes[i] > 1999 && this.runes[i] < 2030) + return this.runes[i]; + return 0; + } + + public int getBaseClass() { + for (int i = 0; i < 23; i++) + if (this.runes[i] > 2499 && this.runes[i] < 2504) + return this.runes[i]; + return 0; + } + + /** + * @return the firstName + */ + public String getFirstName() { + return firstName; + } + + /** + * @param firstName the firstName to set + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * @return the lastName + */ + public String getLastName() { + return lastName; + } + + /** + * @param lastName the lastName to set + */ + public void setLastName(String lastName) { + this.lastName = lastName; + } + + /** + * @return the serverID + */ + public int getServerID() { + return serverID; + } + + /** + * @param serverID the serverID to set + */ + public void setServerID(int serverID) { + this.serverID = serverID; + } + + /** + * @return the hairStyle + */ + public int getHairStyle() { + return hairStyle; + } + + /** + * @param hairStyle the hairStyle to set + */ + public void setHairStyle(int hairStyle) { + this.hairStyle = hairStyle; + } + + /** + * @return the beardStyle + */ + public int getBeardStyle() { + return beardStyle; + } + + /** + * @param beardStyle the beardStyle to set + */ + public void setBeardStyle(int beardStyle) { + this.beardStyle = beardStyle; + } + + /** + * @return the skinColor + */ + public int getSkinColor() { + return skinColor; + } + + /** + * @param skinColor the skinColor to set + */ + public void setSkinColor(int skinColor) { + this.skinColor = skinColor; + } + + /** + * @return the hairColor + */ + public int getHairColor() { + return hairColor; + } + + /** + * @param hairColor the hairColor to set + */ + public void setHairColor(int hairColor) { + this.hairColor = hairColor; + } + + /** + * @return the beardColor + */ + public int getBeardColor() { + return beardColor; + } + + /** + * @param beardColor the beardColor to set + */ + public void setBeardColor(int beardColor) { + this.beardColor = beardColor; + } + + /** + * @return the kit + */ + public int getKit() { + return kit; + } + + /** + * @param kit the kit to set + */ + public void setKit(int kit) { + this.kit = kit; + } + + /** + * @return the runeCount + */ + public int getNumRunes() { + return numRunes; + } + + /** + * @param numRunes the runeCount to set + */ + public void setNumRunes(int numRunes) { + this.numRunes = numRunes; + } + + /** + * @return the runes + */ + public int[] getRunes() { + return runes; + } + + /** + * @param runes the runes to set + */ + public void setRunes(int[] runes) { + this.runes = runes; + } + + /** + * @return the numStats + */ + public int getNumStats() { + return numStats; + } + + /** + * @param numStats the numStats to set + */ + public void setNumStats(int numStats) { + this.numStats = numStats; + } + + /** + * @return the strengthMod + */ + public int getStrengthMod() { + return strengthMod; + } + + /** + * @param strengthMod the strengthMod to set + */ + public void setStrengthMod(int strengthMod) { + this.strengthMod = strengthMod; + } + + /** + * @return the dexterityMod + */ + public int getDexterityMod() { + return dexterityMod; + } + + /** + * @param dexterityMod the dexterityMod to set + */ + public void setDexterityMod(int dexterityMod) { + this.dexterityMod = dexterityMod; + } + + /** + * @return the constitutionMod + */ + public int getConstitutionMod() { + return constitutionMod; + } + + /** + * @param constitutionMod the constitutionMod to set + */ + public void setConstitutionMod(int constitutionMod) { + this.constitutionMod = constitutionMod; + } + + /** + * @return the intelligenceMod + */ + public int getIntelligenceMod() { + return intelligenceMod; + } + + /** + * @param intelligenceMod the intelligenceMod to set + */ + public void setIntelligenceMod(int intelligenceMod) { + this.intelligenceMod = intelligenceMod; + } + + /** + * @return the spiritMod + */ + public int getSpiritMod() { + return spiritMod; + } + + /** + * @param spiritMod the spiritMod to set + */ + public void setSpiritMod(int spiritMod) { + this.spiritMod = spiritMod; + } + +} diff --git a/src/engine/net/client/msg/login/DeleteCharacterMsg.java b/src/engine/net/client/msg/login/DeleteCharacterMsg.java new file mode 100644 index 00000000..ddcf300b --- /dev/null +++ b/src/engine/net/client/msg/login/DeleteCharacterMsg.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class DeleteCharacterMsg extends ClientNetMsg { + + private int characterUUID; + private String firstName; + private String serverName; + + /** + * This is the general purpose constructor. + */ + public DeleteCharacterMsg() { + super(Protocol.REMOVECHAR); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public DeleteCharacterMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.REMOVECHAR, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.characterUUID); + writer.putString(this.firstName); + writer.putString(this.serverName); + } + + /** + * Deserializes the subclass specific items from the supplied + * ByteBufferReader. + */ + + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); // Object Type Padding + this.characterUUID = reader.getInt(); + this.firstName = reader.getString(); + this.serverName = reader.getString(); + } + + /** + * @return the characterUUID + */ + public int getCharacterUUID() { + return characterUUID; + } + +} diff --git a/src/engine/net/client/msg/login/GameServerIPRequestMsg.java b/src/engine/net/client/msg/login/GameServerIPRequestMsg.java new file mode 100644 index 00000000..cbacdb32 --- /dev/null +++ b/src/engine/net/client/msg/login/GameServerIPRequestMsg.java @@ -0,0 +1,62 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + +import engine.Enum.GameObjectType; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class GameServerIPRequestMsg extends ClientNetMsg { + + private int characterUUID; + + /** + * This is the general purpose constructor. + */ + public GameServerIPRequestMsg() { + super(Protocol.SELECTCHAR); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public GameServerIPRequestMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.SELECTCHAR, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(this.characterUUID); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + reader.getInt(); // Object Type Padding + this.characterUUID = reader.getInt(); + } + + /** + * @return the characterUUID + */ + public int getCharacterUUID() { + return characterUUID; + } + +} diff --git a/src/engine/net/client/msg/login/GameServerIPResponseMsg.java b/src/engine/net/client/msg/login/GameServerIPResponseMsg.java new file mode 100644 index 00000000..fe258fc9 --- /dev/null +++ b/src/engine/net/client/msg/login/GameServerIPResponseMsg.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.gameManager.ConfigManager; +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class GameServerIPResponseMsg extends ClientNetMsg { + + private String ip; + private int port; + + /** + * This is the general purpose constructor. + */ + public GameServerIPResponseMsg(String ip, int port) { + super(Protocol.GAMESERVERIPRESPONSE); + this.ip = ip; + this.port = port; + } + + /** + * This is the general purpose constructor. + */ + public GameServerIPResponseMsg() { + super(Protocol.GAMESERVERIPRESPONSE); + this.ip = ConfigManager.MB_EXTERNAL_ADDR.getValue(); + this.port = Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue()); + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public GameServerIPResponseMsg(AbstractConnection origin, + ByteBufferReader reader) { + super(Protocol.GAMESERVERIPRESPONSE, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.ip); + writer.putInt(this.port); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.ip = reader.getString(); + this.port = reader.getInt(); + } + + /** + * @return the ip + */ + public String getIp() { + return ip; + } + + /** + * @return the port + */ + public int getPort() { + return port; + } +} diff --git a/src/engine/net/client/msg/login/InvalidNameMsg.java b/src/engine/net/client/msg/login/InvalidNameMsg.java new file mode 100644 index 00000000..0547abea --- /dev/null +++ b/src/engine/net/client/msg/login/InvalidNameMsg.java @@ -0,0 +1,89 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.server.world.WorldServer; + +public class InvalidNameMsg extends ClientNetMsg { + + private String FirstName; + private String LastName; + private int errorCode; + private int serverID; + + /** + * This is the general purpose constructor. + */ + public InvalidNameMsg(String FirstName, String LastName, + int errorCode) { + super(Protocol.NAMEVERIFY); + this.FirstName = FirstName; + this.LastName = LastName; + this.errorCode = errorCode; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public InvalidNameMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.NAMEVERIFY, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.FirstName); + writer.putString(this.LastName); + writer.putInt(WorldServer.worldMapID); + writer.putInt(errorCode); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.FirstName = reader.getString(); + this.LastName = reader.getString(); + this.serverID = reader.getInt(); + reader.monitorInt(0, "InvalidNameMsg 01"); + } + + + /** + * @return the firstName + */ + public String getFirstName() { + return FirstName; + } + + /** + * @return the lastName + */ + public String getLastName() { + return LastName; + } + + /** + * @return the errorCode + */ + public int getErrorCode() { + return errorCode; + } + +} diff --git a/src/engine/net/client/msg/login/LoginErrorMsg.java b/src/engine/net/client/msg/login/LoginErrorMsg.java new file mode 100644 index 00000000..d5247d96 --- /dev/null +++ b/src/engine/net/client/msg/login/LoginErrorMsg.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class LoginErrorMsg extends ClientNetMsg { + + private int reason; + private String message; + + /** + * This is the general purpose constructor. + */ + public LoginErrorMsg(int Reason, String message) { + super(Protocol.LOGINFAILED); + this.reason = Reason; + this.message = message; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public LoginErrorMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.LOGINFAILED, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.reason); + writer.putString(this.message); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.reason = reader.getInt(); + this.message = reader.getString(); + } + + /** + * @return the reason + */ + public int getReason() { + return reason; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } +} diff --git a/src/engine/net/client/msg/login/ServerStatusMsg.java b/src/engine/net/client/msg/login/ServerStatusMsg.java new file mode 100644 index 00000000..32d96198 --- /dev/null +++ b/src/engine/net/client/msg/login/ServerStatusMsg.java @@ -0,0 +1,80 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class ServerStatusMsg extends ClientNetMsg { + + private int serverID; + private byte isUp; + + /** + * This is the general purpose constructor. + */ + public ServerStatusMsg(int serverID, byte isUp) { + super(Protocol.ARCSERVERSTATUS); + this.serverID = serverID; + this.isUp = isUp; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the + * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading + * past the limit) then this constructor Throws that Exception to the + * caller. + */ + public ServerStatusMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.ARCSERVERSTATUS, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied NetMsgWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putInt(this.serverID); + writer.put(this.isUp); + } + + /** + * Deserializes the subclass specific items from the supplied NetMsgReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.serverID = reader.getInt(); + this.isUp = reader.get(); + } + + public int getServerID() { + return this.serverID; + } + + public void setServerID(int value) { + this.serverID = value; + } + + public byte getIsUp() { + return this.isUp; + } + + public void setIsUp(byte value) { + this.isUp = value; + } + + public boolean isUp() { + return (this.isUp == 0x01); + } +} diff --git a/src/engine/net/client/msg/login/VersionInfoMsg.java b/src/engine/net/client/msg/login/VersionInfoMsg.java new file mode 100644 index 00000000..81852009 --- /dev/null +++ b/src/engine/net/client/msg/login/VersionInfoMsg.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.net.client.msg.login; + + +import engine.net.AbstractConnection; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; + +public class VersionInfoMsg extends ClientNetMsg { + + private String majorVersion; + private String minorVersion; + + /** + * This is the general purpose constructor. + */ + public VersionInfoMsg(String majorVersion, String minorVersion) { + super(Protocol.VERSIONINFO); + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + } + + /** + * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller. + */ + public VersionInfoMsg(AbstractConnection origin, ByteBufferReader reader) { + super(Protocol.VERSIONINFO, origin, reader); + } + + /** + * Serializes the subclass specific items to the supplied ByteBufferWriter. + */ + @Override + protected void _serialize(ByteBufferWriter writer) { + writer.putString(this.majorVersion); + writer.putString(this.minorVersion); + } + + /** + * Deserializes the subclass specific items from the supplied ByteBufferReader. + */ + @Override + protected void _deserialize(ByteBufferReader reader) { + this.majorVersion = reader.getString(); + this.minorVersion = reader.getString(); + } + + /** + * @return the majorVersion + */ + public String getMajorVersion() { + return majorVersion; + } + + /** + * @return the minorVersion + */ + public String getMinorVersion() { + return minorVersion; + } +} diff --git a/src/engine/objects/AbstractCharacter.java b/src/engine/objects/AbstractCharacter.java new file mode 100644 index 00000000..e43da320 --- /dev/null +++ b/src/engine/objects/AbstractCharacter.java @@ -0,0 +1,1869 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.*; +import engine.InterestManagement.InterestManager; +import engine.InterestManagement.WorldGrid; +import engine.exception.SerializationException; +import engine.gameManager.CombatManager; +import engine.gameManager.ConfigManager; +import engine.gameManager.MovementManager; +import engine.gameManager.PowersManager; +import engine.job.AbstractJob; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.ChantJob; +import engine.jobs.PersistentAoeJob; +import engine.jobs.TrackJob; +import engine.math.AtomicFloat; +import engine.math.Bounds; +import engine.math.Vector3fImmutable; +import engine.net.ByteBufferWriter; +import engine.net.DispatchMessage; +import engine.net.client.msg.UpdateStateMsg; +import engine.powers.EffectsBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public abstract class AbstractCharacter extends AbstractWorldObject { + + protected CharacterItemManager charItemManager; + private final ReentrantReadWriteLock healthLock = new ReentrantReadWriteLock(); + public short level; + public AbstractWorldObject combatTarget; + public int contractUUID; + public Contract contract; + + protected String firstName; + protected String lastName; + protected short statStrCurrent; + protected short statDexCurrent; + protected short statConCurrent; + protected short statIntCurrent; + protected short statSpiCurrent; + protected short unusedStatPoints; + protected int exp; + public int buildingUUID; + public Building building; + + public Vector3fImmutable bindLoc; + protected Vector3fImmutable faceDir; + + public int guildUUID; + public Guild guild; + protected byte runningTrains; + protected ConcurrentHashMap powers; + protected ConcurrentHashMap skills; + // Variables NOT to be stored in db + protected boolean sit = false; + protected boolean walkMode; + protected boolean combat = false; + public Vector3fImmutable endLoc = Vector3fImmutable.ZERO; + protected boolean itemCasting = false; + // nextEndLoc is used to store the next end location when someone is clicking + // around the ground while other timers like changeAltitude are still + // ticking down so that mobs/players following dont just move away to your projected location + protected Vector3fImmutable nextEndLoc = Vector3fImmutable.ZERO; + protected float speed; + protected AtomicFloat stamina = new AtomicFloat(); + protected float staminaMax; + protected AtomicFloat mana = new AtomicFloat(); + protected float manaMax; // Health/Mana/Stamina + protected AtomicBoolean isAlive = new AtomicBoolean(true); + protected Resists resists = new Resists("Genric"); + protected ConcurrentHashMap timers; + protected ConcurrentHashMap timestamps; + protected int atrHandOne; + protected int atrHandTwo; + protected int minDamageHandOne; + protected int maxDamageHandOne; + protected int minDamageHandTwo; + protected int maxDamageHandTwo; + protected float rangeHandOne; + protected float rangeHandTwo; + protected float speedHandOne; + protected float speedHandTwo; + protected int defenseRating; + protected boolean isActive; // <-Do not use this for deleting character! + protected float altitude = 0; // 0=on terrain, 1=tier 1, 2=tier 2, etc. + protected ConcurrentHashMap recycleTimers; + protected PlayerBonuses bonuses; + protected JobContainer lastChant; + protected boolean isCasting = false; + protected long lastSetLocUpdate = 0L; + protected int inBuilding = -1; // -1 not in building 0 on ground floor, 1 on first floor etc + protected int inBuildingID = 0; + protected int inFloorID = -1; + protected int liveCounter = 0; + protected int debug = 0; + protected boolean movingUp = false; + private float desiredAltitude = 0; + private long takeOffTime = 0; + private float hateValue = 0; + private long lastHateUpdate = 0; + private boolean collided = false; + private byte aoecntr = 0; + + public AbstractCharacter() { + super(); + + this.powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.initializeCharacter(); + + } + + /** + * No Id Constructor + */ + public AbstractCharacter( + final String firstName, + final String lastName, + final short statStrCurrent, + final short statDexCurrent, + final short statConCurrent, + final short statIntCurrent, + final short statSpiCurrent, + final short level, + final int exp, + final Vector3fImmutable bindLoc, + final Vector3fImmutable faceDir, + final Guild guild, + final byte runningTrains + ) { + super(); + this.firstName = firstName; + this.lastName = lastName; + + this.statStrCurrent = statStrCurrent; + this.statDexCurrent = statDexCurrent; + this.statConCurrent = statConCurrent; + this.statIntCurrent = statIntCurrent; + this.statSpiCurrent = statSpiCurrent; + this.level = level; + this.exp = exp; + this.walkMode = true; + this.bindLoc = bindLoc; + this.faceDir = faceDir; + this.guild = guild; + this.runningTrains = runningTrains; + this.powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.initializeCharacter(); + + } + + /** + * Normal Constructor + */ + public AbstractCharacter( + final String firstName, + final String lastName, + final short statStrCurrent, + final short statDexCurrent, + final short statConCurrent, + final short statIntCurrent, + final short statSpiCurrent, + final short level, + final int exp, + final Vector3fImmutable bindLoc, + final Vector3fImmutable currentLoc, + final Vector3fImmutable faceDir, + final Guild guild, + final byte runningTrains, + final int newUUID + ) { + + super(newUUID); + this.firstName = firstName; + this.lastName = lastName; + + this.statStrCurrent = statStrCurrent; + this.statDexCurrent = statDexCurrent; + this.statConCurrent = statConCurrent; + this.statIntCurrent = statIntCurrent; + this.statSpiCurrent = statSpiCurrent; + this.level = level; + this.exp = exp; + this.walkMode = true; + + this.bindLoc = bindLoc; + this.faceDir = faceDir; + this.guild = guild; + + this.runningTrains = runningTrains; + this.powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.initializeCharacter(); + + // Dangerous to use THIS in a constructor!!! + this.charItemManager = new CharacterItemManager(this); + } + + /** + * ResultSet Constructor for players + */ + public AbstractCharacter( + final ResultSet rs, + final boolean isPlayer + ) throws SQLException { + super(rs); + + this.firstName = rs.getString("char_firstname"); + this.lastName = rs.getString("char_lastname"); + + this.level = 1; + this.exp = rs.getInt("char_experience"); + this.walkMode = false; + + this.bindLoc = new Vector3fImmutable(0f, 0f, 0f); + this.endLoc = Vector3fImmutable.ZERO; + + this.faceDir = Vector3fImmutable.ZERO; + + final int guildID = rs.getInt("GuildUID"); + final Guild errantGuild = Guild.getErrantGuild(); + + if (guildID == errantGuild.getObjectUUID()) { + this.guild = errantGuild; + } else { + this.guild = Guild.getGuild(guildID); + if (this.guild == null) { + this.guild = Guild.getErrantGuild(); + } + } + + if (this.guild == null) + this.guild = errantGuild; + + this.skills = new ConcurrentHashMap<>(); + this.powers = new ConcurrentHashMap<>(); + this.initializeCharacter(); + + // Dangerous to use THIS in a constructor!!! + this.charItemManager = new CharacterItemManager(this); + } + + /** + * ResultSet Constructor for NPC/Mobs + */ + public AbstractCharacter(final ResultSet rs) throws SQLException { + super(rs); + + this.firstName = ""; + this.lastName = ""; + + this.statStrCurrent = (short) 0; + this.statDexCurrent = (short) 0; + this.statConCurrent = (short) 0; + this.statIntCurrent = (short) 0; + this.statSpiCurrent = (short) 0; + + this.unusedStatPoints = (short) 0; + + this.level = (short) 0; // TODO get this from MobsBase later + this.exp = 1; + this.walkMode = true; + this.bindLoc = Vector3fImmutable.ZERO; + this.faceDir = Vector3fImmutable.ZERO; + + this.runningTrains = (byte) 0; + + this.skills = new ConcurrentHashMap<>(); + this.powers = new ConcurrentHashMap<>(); + initializeCharacter(); + + // Dangerous to use THIS in a constructor!!! + this.charItemManager = new CharacterItemManager(this); + } + + /** + * ResultSet Constructor for static Mobs + */ + public AbstractCharacter(final ResultSet rs, final int objectUUID) throws SQLException { + + super(objectUUID); + + this.firstName = ""; + this.lastName = ""; + + this.statStrCurrent = (short) 0; + this.statDexCurrent = (short) 0; + this.statConCurrent = (short) 0; + this.statIntCurrent = (short) 0; + this.statSpiCurrent = (short) 0; + + this.unusedStatPoints = (short) 0; + + this.level = (short) 0; // TODO get this from MobsBase later + this.exp = 1; + this.walkMode = true; + + this.bindLoc = new Vector3fImmutable(rs.getFloat("spawnX"), rs.getFloat("spawnY"), rs.getFloat("spawnZ")); + + if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER)) + this.setLoc(this.bindLoc); + this.endLoc = Vector3fImmutable.ZERO; + + + this.faceDir = Vector3fImmutable.ZERO; + + final int guildID = rs.getInt("GuildID"); + + if (guildID == Guild.getErrantGuild().getObjectUUID()) { + this.guild = Guild.getErrantGuild(); + } else { + this.guild = Guild.getGuild(guildID); + } + + if (this.guild == null) + this.guild = Guild.getErrantGuild(); + + this.runningTrains = (byte) 0; + this.skills = new ConcurrentHashMap<>(); + this.powers = new ConcurrentHashMap<>(); + + this.initializeCharacter(); + + // Dangerous to use THIS in a constructor!!! + this.charItemManager = new CharacterItemManager(this); + } + + public static int getBankCapacity() { + return 500; + } + + public static int getVaultCapacity() { + return 5000; + } + + public static void _serializeForClientMsg(AbstractCharacter abstractCharacter, final ByteBufferWriter writer) throws SerializationException { + AbstractCharacter.__serializeForClientMsg(abstractCharacter, writer); + } + + public static void __serializeForClientMsg(AbstractCharacter abstractCharacter, final ByteBufferWriter writer) throws SerializationException { + } + + public static void serializeForClientMsgOtherPlayer(AbstractCharacter abstractCharacter, final ByteBufferWriter writer, final boolean asciiLastName) throws SerializationException { + + switch (abstractCharacter.getObjectType()) { + case PlayerCharacter: + PlayerCharacter.serializePlayerForClientMsgOtherPlayer((PlayerCharacter) abstractCharacter, writer, asciiLastName); + break; + case Mob: + Mob.serializeMobForClientMsgOtherPlayer((Mob) abstractCharacter, writer); + break; + case NPC: + NPC.serializeNpcForClientMsgOtherPlayer((NPC) abstractCharacter, writer, asciiLastName); + break; + } + + + //TODO INPUT SWITCH CASE ON GAME OBJECTS TO CALL SPECIFIC METHODS. + } + + public static final void serializeForTrack(AbstractCharacter abstractCharacter, final ByteBufferWriter writer, boolean isGroup) { + writer.putInt(abstractCharacter.getObjectType().ordinal()); + writer.putInt(abstractCharacter.getObjectUUID()); + + if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) { + writer.putString(abstractCharacter.getName()); + } else { + writer.putString(abstractCharacter.getFirstName()); + } + writer.put(isGroup ? (byte) 1 : (byte) 0); + if (abstractCharacter.guild != null) { + Guild.serializeForTrack(abstractCharacter.guild, writer); + } else { + Guild.serializeErrantForTrack(writer); + } + } + + public static void runBonusesOnLoad(PlayerCharacter playerCharacter) { + + // synchronized with getBonuses() + + synchronized (playerCharacter.bonuses) { + try { + //run until no new bonuses are applied + + // clear bonuses and reapply rune bonuses + if (playerCharacter.getObjectType() == GameObjectType.PlayerCharacter) { + playerCharacter.bonuses.calculateRuneBaseEffects(playerCharacter); + } else { + playerCharacter.bonuses.clearRuneBaseEffects(); + } + + // apply effect bonuses + + for (Effect eff : playerCharacter.effects.values()) { + eff.applyBonus(playerCharacter); + } + + //apply item bonuses for equipped items + ConcurrentHashMap equip = null; + + if (playerCharacter.charItemManager != null) + equip = playerCharacter.charItemManager.getEquipped(); + + if (equip != null) { + for (Item item : equip.values()) { + item.clearBonuses(); + if (item != null) { + ConcurrentHashMap effects = item.getEffects(); + if (effects != null) { + for (Effect eff : effects.values()) { + eff.applyBonus(item, playerCharacter); + } + } + } + } + } + + //recalculate passive defenses + playerCharacter.setPassives(); + + //flip the active bonus set for synchronization purposes + //do this after all bonus updates, but before recalculations. + // recalculate everything + //calculate item bonuses + playerCharacter.calculateItemBonuses(); + + //recalculate formulas + PlayerCharacter.recalculatePlayerStatsOnLoad(playerCharacter); + + } catch (Exception e) { + Logger.error(e); + } + + } + + } + + public static Regions InsideBuildingRegionGoingDown(AbstractCharacter player) { + + HashSet buildings = WorldGrid.getObjectsInRangePartial(player, 1000, MBServerStatics.MASK_BUILDING); + + Regions tempRegion = null; + for (AbstractWorldObject awo : buildings) { + + Building building = (Building) awo; + if (building.getBounds() == null) + continue; + + if (!Bounds.collide(player.getLoc(), building.getBounds())) + continue; + + for (Regions region : building.getBounds().getRegions()) { + + if (!region.isPointInPolygon(player.getLoc())) + continue; + + if (!region.isOutside()) + continue; + if (tempRegion == null) + tempRegion = region; + + if (tempRegion.highLerp.y < region.highLerp.y) + tempRegion = region; + } + + if (tempRegion != null) + break; + } + return tempRegion; + } + + public static boolean CanFly(AbstractCharacter flyer) { + boolean canFly = false; + PlayerBonuses bonus = flyer.getBonuses(); + + if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.Fly) && bonus.getBool(ModType.Fly, SourceType.None) && flyer.isAlive()) + canFly = true; + + return canFly; + + } + + public static void teleport(AbstractCharacter worldObject, final Vector3fImmutable targetLoc) { + Regions targetRegion = Regions.GetRegionForTeleport(targetLoc); + worldObject.locationLock.writeLock().lock(); + try { + MovementManager.translocate(worldObject, targetLoc, targetRegion); + if (worldObject.getObjectType().equals(GameObjectType.PlayerCharacter)) + InterestManager.INTERESTMANAGER.HandleLoadForTeleport((PlayerCharacter) worldObject); + } catch (Exception e) { + Logger.error(e); + } finally { + worldObject.locationLock.writeLock().unlock(); + } + } + + private void initializeCharacter() { + this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.timestamps = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + final long l = System.currentTimeMillis(); + this.timestamps.put("Health Recovery", l); + this.timestamps.put("Stamina Recovery", l); + this.timestamps.put("Mana Recovery", l); + this.recycleTimers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + } + + protected abstract ConcurrentHashMap initializePowers(); + + public final void addPersistantAoe( + final String name, + final int duration, + final PersistentAoeJob asj, + final EffectsBase eb, + final int trains + ) { + if (!isAlive()) { + return; + } + final JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration); + final Effect eff = new Effect(jc, eb, trains); + aoecntr++; + this.effects.put(name + aoecntr, eff); + eff.setPAOE(); + } + + public final void setLastChant( + final int duration, + final ChantJob cj + ) { + if (!isAlive()) { + return; + } + if (this.lastChant != null) { + this.lastChant.cancelJob(); + } + this.lastChant = JobScheduler.getInstance().scheduleJob(cj, duration); + } + + public final void cancelLastChant() { + if (this.lastChant != null) { + this.lastChant.cancelJob(); + this.lastChant = null; + } + } + + public final void cancelLastChantIfSame(final Effect eff) { + if (eff == null || this.lastChant == null) { + return; + } + final AbstractJob aj = this.lastChant.getJob(); + if (aj == null || (!(aj instanceof ChantJob))) { + return; + } + final int token = ((ChantJob) aj).getPowerToken(); + if (eff.getPowerToken() == token && token != 0) { + this.cancelLastChant(); + } + } + + /* + * Getters + */ + public final short getUnusedStatPoints() { + return this.unusedStatPoints; + } + + public final CharacterItemManager getCharItemManager() { + return this.charItemManager; + } + + public final void setDebug( + final int value, + final boolean toggle + ) { + if (toggle) { + this.debug |= value; //turn on debug + } else { + this.debug &= ~value; //turn off debug + } + } + + public final boolean getDebug(final int value) { + return ((this.debug & value) != 0); + } + + @Override + public String getName() { + if (this.firstName.length() == 0 && this.lastName.length() == 0) { + return "Unnamed " + '(' + this.getObjectUUID() + ')'; + } else if (this.lastName.length() == 0) { + return this.getFirstName(); + } else { + return this.getFirstName() + ' ' + this.getLastName(); + } + } + + public String getFirstName() { + return this.firstName; + } + + public void setFirstName(final String name) { + this.firstName = name; + } + + public String getLastName() { + return this.lastName; + } + + public void setLastName(final String name) { + this.lastName = name; + } + + public final short getStatStrCurrent() { + return this.statStrCurrent; + } + + public final short getStatDexCurrent() { + return this.statDexCurrent; + } + + public final short getStatConCurrent() { + return this.statConCurrent; + } + + public final short getStatIntCurrent() { + return this.statIntCurrent; + } + + public final short getStatSpiCurrent() { + return this.statSpiCurrent; + } + + public short getLevel() { + return this.level; + } + + public void setLevel(final short value) { + this.level = value; + } + + public final boolean isActive() { + return this.isActive; + } + + public final void setActive(final boolean value) { + this.isActive = value; + } + + public final Resists getResists() { + if (this.resists == null) + return Resists.getResists(0); + return this.resists; + } + + public final void setResists(final Resists value) { + this.resists = value; + } + + public final int getExp() { + return this.exp; + } + + public final JobContainer getLastPower() { + if (this.timers == null) { + return null; + } + return this.timers.get("LastPower"); + } + + public final void setLastPower(final JobContainer jc) { + if (this.timers != null) { + this.timers.put("LastPower", jc); + } + } + + public final void clearLastPower() { + if (this.timers != null) { + this.timers.remove("LastPower"); + } + } + + public final JobContainer getLastItem() { + if (this.timers == null) { + return null; + } + return this.timers.get("LastItem"); + } + + public final int getIsSittingAsInt() { + if (!this.isAlive()) { + return 1; + } + + if (this.sit) { + return 4; + } else { + if (this.isMoving()) + return 7; + else + return 5; + } + } + + public final int getIsWalkingAsInt() { + if (this.walkMode) { + return 1; + } + return 2; + } + + public final int getIsCombatAsInt() { + if (this.combat) { + return 2; + } + return 1; + } + + public final int getIsFlightAsInt() { + if (this.altitude > 0) { + return 3; + } + + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) + if (((PlayerCharacter) this).isLastSwimming()) + return 1; //swimming + + return 2; //ground + } + + public final void clearTimer(final String name) { + if (this.timers != null) { + this.timers.remove(name); + } + } + + public abstract Vector3fImmutable getBindLoc(); + + public final void setBindLoc(final Vector3fImmutable value) { + this.bindLoc = value; + } + + public final Vector3fImmutable getFaceDir() { + return this.faceDir; + } + + public final void setFaceDir(final Vector3fImmutable value) { + this.faceDir = value; + } + + public final Vector3fImmutable getEndLoc() { + return this.endLoc; + } + + public final void setEndLoc(final Vector3fImmutable value) { + if (value.x > MBServerStatics.MAX_PLAYER_X_LOC) + return; + if (value.z < MBServerStatics.MAX_PLAYER_Y_LOC) + return; + + this.endLoc = value; + // reset the location timer so our next call to getLoc is correct + this.resetLastSetLocUpdate(); + } + + public final Vector3fImmutable getNextEndLoc() { + // this is only used when users are changing their end + // location while a timer like changeAltitude is ticking down + return this.nextEndLoc; + } + + public final void stopMovement(Vector3fImmutable stopLoc) { + + + locationLock.writeLock().lock(); + + try { + this.setLoc(stopLoc); + this.endLoc = Vector3fImmutable.ZERO; + this.resetLastSetLocUpdate(); + } catch (Exception e) { + Logger.error(e); + } finally { + locationLock.writeLock().unlock(); + } + } + + public final boolean isMoving() { + + // I might be on my way but my movement is paused + // due to a flight alt change + //TODO who the fuck wrote changeHeightJob. FIX THIS. + + + if (this.endLoc.equals(Vector3fImmutable.ZERO) || this.endLoc.equals(this.bindLoc)) + return false; + + if (this.takeOffTime != 0) + return false; + + if (this.isCasting && this.getObjectType().equals(GameObjectType.PlayerCharacter)) + return false; + + return true; + } + + public final boolean useFlyMoveRegen() { + + + if (this.endLoc.x != 0 && this.endLoc.z != 0) + return true; + + return false; + } + + public boolean asciiLastName() { + return true; + } + + public final ConcurrentHashMap getSkills() { + return this.skills; + } + + public final ConcurrentHashMap getPowers() { + return this.powers; + } + + public final int getInBuilding() { + return this.inBuilding; + } + + public final void setInBuilding(final int floor) { + this.inBuilding = floor; + } + + public Guild getGuild() { + return this.guild; + } + + /* + * Setters + */ + public void setGuild(final Guild value) { + this.guild = value; + } + + public int getGuildUUID() { + return this.guild.getObjectUUID(); + } + + public final int getRank() { + return (this.level / 10); + } + + public final int getAtrHandOne() { + return this.atrHandOne; + } + + public final int getAtrHandTwo() { + return this.atrHandTwo; + } + + public final int getMinDamageHandOne() { + return this.minDamageHandOne; + } + + public final int getMaxDamageHandOne() { + return this.maxDamageHandOne; + } + + public final int getMinDamageHandTwo() { + return this.minDamageHandTwo; + } + + public final int getMaxDamageHandTwo() { + return this.maxDamageHandTwo; + } + + public final int getDefenseRating() { + return this.defenseRating; + } + + public final float getSpeedHandOne() { + return this.speedHandOne; + } + + public final float getSpeedHandTwo() { + return this.speedHandTwo; + } + + public final float getRange() { + + // Treb range does not appear to be set here + // what gives? + + + if (this.getObjectType() == GameObjectType.Mob) { + Mob mob = (Mob) this; + if (mob.isSiege()) { + return 300; + } + float range = 8; + if (((Mob) this).getEquip().get(1) != null) { + range = ((Mob) this).getEquip().get(1).getItemBase().getRange(); + } else if (((Mob) this).getEquip().get(2) != null) { + range = ((Mob) this).getEquip().get(2).getItemBase().getRange(); + } + if (range > 80) { + range = 80; + } + return range; + } + if (this.rangeHandOne > this.rangeHandTwo) { + return this.rangeHandOne; + } + return this.rangeHandTwo; + } + + public abstract float getPassiveChance( + final String type, + final int attackerLevel, + final boolean fromCombat); + + public abstract float getSpeed(); + + public final int getBankCapacityRemaining() { + return (AbstractCharacter.getBankCapacity() - this.charItemManager.getBankWeight()); + } + + public final int getVaultCapacityRemaining() { + return (AbstractCharacter.getVaultCapacity() - this.charItemManager.getVaultWeight()); + } + + public final ArrayList getInventory() { + return this.getInventory(false); + } + /* + * Utils + */ + + public final ArrayList getInventory(final boolean getGold) { + if (this.charItemManager == null) { + return new ArrayList<>(); + } + return this.charItemManager.getInventory(getGold); + } + + @Override + public Vector3fImmutable getLoc() { + + return super.getLoc(); + } + + @Override + public final void setLoc(final Vector3fImmutable value) { + super.setLoc(value); // set the location in the world + this.resetLastSetLocUpdate(); + } + + public Vector3fImmutable getMovementLoc() { + + if (this.endLoc.equals(Vector3fImmutable.ZERO)) + return super.getLoc(); + if (this.takeOffTime != 0) + return super.getLoc(); + + return super.getLoc().moveTowards(this.endLoc, this.getSpeed() * ((System.currentTimeMillis() - lastSetLocUpdate) * .001f)); + + } + + public final void setBindLoc(final float x, final float y, final float z) { + this.bindLoc = new Vector3fImmutable(x, y, z); + } + + public final void resetLastSetLocUpdate() { + this.lastSetLocUpdate = System.currentTimeMillis(); + } + + public void setIsCasting(final boolean isCasting) { + this.isCasting = isCasting; + } + + public final boolean isCasting() { + return this.isCasting; + } + + @Override + public final boolean isAlive() { + return this.isAlive.get(); + } + + public final boolean isSafeMode() { + + if (this.resists == null) + return false; + + for (Effect eff : this.getEffects().values()) { + if (eff.getEffectToken() == -1661750486) + return true; + } + return this.resists.immuneToAll(); + } + + public abstract void killCharacter(final AbstractCharacter killer); + + public abstract void killCharacter(final String reason); + + /** + * Determines if the character is in a lootable state. + * + * @return True if lootable. + */ + public abstract boolean canBeLooted(); + + public float calcHitBox() { + if (this.getObjectType() == GameObjectType.PlayerCharacter) { + // hit box radius is str/100 (gets diameter of hitbox) /2 (as we want a radius) + // note this formula is guesswork + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) { + Logger.info("Hit box radius for " + this.getFirstName() + " is " + (this.statStrCurrent / 200f)); + } + return ((PlayerCharacter) this).getStrForClient() / 200f; + //TODO CALCULATE MOB HITBOX BECAUSE FAIL EMU IS FAIL!!!!!!! + } else if (this.getObjectType() == GameObjectType.Mob) { + if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) { + Logger.info("Hit box radius for " + this.getFirstName() + " is " + ((Mob) this).getMobBase().getHitBoxRadius()); + } + return ((Mob) this).getMobBase().getHitBoxRadius(); + } + return 0f; + } + + public final boolean isSit() { + return this.sit; + } + + public final void setSit(final boolean value) { + + if (this.sit != value) { + // change sit/stand and sync location + this.sit = value; + if (value == true) // we have been told to sit + { + this.stopMovement(this.getLoc()); + } + } + + } + + public final boolean isWalk() { + return this.walkMode; + } + + public final boolean isCombat() { + return this.combat; + } + + public final void setCombat(final boolean value) { + this.combat = value; + } + + public final void setWalkMode(final boolean value) { + // sync movement location as getLoc gets where we are at the exact moment in time (i.e. not the last updated loc) + this.setLoc(this.getLoc()); + if (this.walkMode == value) { + return; + } else { + this.walkMode = value; + } + } + + public final AbstractWorldObject getCombatTarget() { + return this.combatTarget; + } + + public final void setCombatTarget(final AbstractWorldObject value) { + if(this.getObjectTypeMask() == 2050) {//MOB? + if (value == null) { + if (this.isCombat()) { + this.setCombat(false); + UpdateStateMsg rwss = new UpdateStateMsg(); + rwss.setPlayer(this); + DispatchMessage.sendToAllInRange(this, rwss); + } + }else { + if (!this.isCombat()) { + this.setCombat(true); + UpdateStateMsg rwss = new UpdateStateMsg(); + rwss.setPlayer(this); + DispatchMessage.sendToAllInRange(this, rwss); + } + } + } + this.combatTarget = value; + } + + public final ConcurrentHashMap getTimers() { + if (this.timers == null) { + this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + } + return this.timers; + } + + public final int getLiveCounter() { + return this.liveCounter; + } + + public final void addTimer( + final String name, + final AbstractJob asj, + final int duration + ) { + final JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration); + if (this.timers == null) { + this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + } + this.timers.put(name, jc); + } + + public final void renewTimer( + final String name, + final AbstractJob asj, + final int duration + ) { + this.cancelTimer(name); + this.addTimer(name, asj, duration); + } + + public final ConcurrentHashMap getRecycleTimers() { + return this.recycleTimers; + } + + public final ConcurrentHashMap getTimestamps() { + return this.timestamps; + } + + public final long getTimeStamp(final String name) { + if (this.timestamps.containsKey(name)) { + return this.timestamps.get(name); + } + return 0L; + } + + public final void setTimeStamp(final String name, final long value) { + this.timestamps.put(name, value); + } + + public final void setTimeStampNow(final String name) { + this.timestamps.put(name, System.currentTimeMillis()); + } + + public final void cancelTimer(final String name) { + cancelTimer(name, true); + } + + public final void cancelTimer(final String name, final boolean jobRunning) { + if (this.timers == null) { + this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + } + if (this.timers.containsKey(name)) { + if (jobRunning) { + this.timers.get(name).cancelJob(); + } + this.timers.remove(name); + } + } + + public final float modifyHealth( + final float value, + final AbstractCharacter attacker, + final boolean fromCost) { + + try { + + try { + boolean ready = this.healthLock.writeLock().tryLock(1, TimeUnit.SECONDS); + + while (!ready) + ready = this.healthLock.writeLock().tryLock(1, TimeUnit.SECONDS); + + if (!this.isAlive()) + return 0; + + Float oldHealth, newHealth; + + if (!this.isAlive()) + return 0f; + + oldHealth = this.health.get(); + newHealth = oldHealth + value; + + if (newHealth > this.healthMax) + newHealth = healthMax; + + this.health.set(newHealth); + + if (newHealth <= 0) { + if (this.isAlive.compareAndSet(true, false)) { + killCharacter(attacker); + return newHealth - oldHealth; + } else + return 0f; //already dead, don't send damage again + } // past this lock! + + //TODO why is Handle REtaliate and cancelontakedamage in modifyHealth? shouldnt this be outside this method? + if (value < 0f && !fromCost) { + this.cancelOnTakeDamage(); + CombatManager.handleRetaliate(this, attacker); + } + + return newHealth - oldHealth; + } finally { + this.healthLock.writeLock().unlock(); + } + + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return 0; + } + + public float getCurrentHitpoints() { + return this.health.get(); + } + + public final float modifyMana( + final float value, + final AbstractCharacter attacker + ) { + return this.modifyMana(value, attacker, false); + } + + public final float modifyMana( + final float value, + final AbstractCharacter attacker, + final boolean fromCost + ) { + + if (!this.isAlive()) { + return 0f; + } + boolean worked = false; + Float oldMana = 0f, newMana = 0f; + while (!worked) { + oldMana = this.mana.get(); + newMana = oldMana + value; + if (newMana > this.manaMax) { + newMana = manaMax; + } else if (newMana < 0) { + newMana = 0f; + } + worked = this.mana.compareAndSet(oldMana, newMana); + } + if (value < 0f && !fromCost) { + this.cancelOnTakeDamage(); + CombatManager.handleRetaliate(this, attacker); + } + return newMana - oldMana; + } + + /* + * Serializing + */ + + public final float modifyStamina( + final float value, + final AbstractCharacter attacker + ) { + return this.modifyStamina(value, attacker, false); + } + + public final float modifyStamina( + final float value, + final AbstractCharacter attacker, + final boolean fromCost + ) { + + if (!this.isAlive()) { + return 0f; + } + boolean worked = false; + Float oldStamina = 0f, newStamina = 0f; + while (!worked) { + oldStamina = this.stamina.get(); + newStamina = oldStamina + value; + if (newStamina > this.staminaMax) { + newStamina = staminaMax; + } else if (newStamina < 0) { + newStamina = 0f; + } + worked = this.stamina.compareAndSet(oldStamina, newStamina); + } + if (value < 0f && !fromCost) { + this.cancelOnTakeDamage(); + CombatManager.handleRetaliate(this, attacker); + } + return newStamina - oldStamina; + } + + public final float setMana( + final float value, + final AbstractCharacter attacker + ) { + return setMana(value, attacker, false); + } + + public final float setMana( + final float value, + final AbstractCharacter attacker, + final boolean fromCost + ) { + + if (!this.isAlive()) { + return 0f; + } + boolean worked = false; + Float oldMana = 0f, newMana = 0f; + while (!worked) { + oldMana = this.mana.get(); + newMana = value; + if (newMana > this.manaMax) { + newMana = manaMax; + } else if (newMana < 0) { + newMana = 0f; + } + worked = this.mana.compareAndSet(oldMana, newMana); + } + if (oldMana > newMana && !fromCost) { + this.cancelOnTakeDamage(); + CombatManager.handleRetaliate(this, attacker); + } + return newMana - oldMana; + } + + public final float setStamina( + final float value, + final AbstractCharacter attacker + ) { + return setStamina(value, attacker, false); + } + + public final float setStamina( + final float value, + final AbstractCharacter attacker, + final boolean fromCost + ) { + + if (!this.isAlive()) { + return 0f; + } + boolean worked = false; + Float oldStamina = 0f, newStamina = 0f; + while (!worked) { + oldStamina = this.stamina.get(); + newStamina = value; + if (newStamina > this.staminaMax) { + newStamina = staminaMax; + } else if (newStamina < 0) { + newStamina = 0f; + } + worked = this.stamina.compareAndSet(oldStamina, newStamina); + } + if (oldStamina > newStamina && !fromCost) { + this.cancelOnTakeDamage(); + CombatManager.handleRetaliate(this, attacker); + } + return newStamina - oldStamina; + + } + + public final float getStamina() { + if (this.getObjectType() == GameObjectType.Mob) + return this.getStaminaMax(); + return this.stamina.get(); + } + + public final float getMana() { + if (this.getObjectType() == GameObjectType.Mob) + return this.getManaMax(); + return this.mana.get(); + } + + public final float getStaminaMax() { + if (this.getObjectType() == GameObjectType.Mob) + return 2000; + return this.staminaMax; + } + + public final float getManaMax() { + if (this.getObjectType() == GameObjectType.Mob) + return 2000; + return this.manaMax; + } + + public final PlayerBonuses getBonuses() { + return this.bonuses; + } + + public void teleport(final Vector3fImmutable targetLoc) { + Regions targetRegion = Regions.GetRegionForTeleport(targetLoc); + locationLock.writeLock().lock(); + try { + MovementManager.translocate(this, targetLoc, targetRegion); + MovementManager.sendRWSSMsg(this); + } catch (Exception e) { + Logger.error(e); + } finally { + locationLock.writeLock().unlock(); + } + } + + /* + * Cancel effects upon actions + */ + public final void cancelOnAttack() { // added to one spot + + boolean changed = false; + + for (String s : this.effects.keySet()) { + + Effect eff = this.effects.get(s); + + if (eff == null) + continue; + if (eff.cancelOnAttack() && eff.cancel()) { + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + + if (changed) { + applyBonuses(); + } + + PowersManager.cancelOnAttack(this); + } + + public final void cancelOnAttackSwing() { // added + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.cancelOnAttackSwing() && eff.cancel()) { + //System.out.println("canceling on AttackSwing"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnAttackSwing(this); + } + + public final void cancelOnCast() { + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + + if (eff == null) + continue; + if (eff.cancelOnCast() && eff.cancel()) { + + // Don't cancel the track effect on the character being tracked + if (eff.getJob() != null && eff.getJob() instanceof TrackJob) { + if (((TrackJob) eff.getJob()).getSource().getObjectUUID() + == this.getObjectUUID()) { + continue; + } + } + + //System.out.println("canceling on Cast"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnCast(this); + } + + public final void cancelOnSpell() { + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.cancelOnCastSpell() && eff.cancel()) { + //System.out.println("canceling on CastSpell"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnSpell(this); + } + + public final void cancelOnMove() { // added + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.cancelOnMove() && eff.cancel()) { + //System.out.println("canceling on Move"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnMove(this); + } + + public final void cancelOnSit() { // added + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.cancelOnSit() && eff.cancel()) { + //System.out.println("canceling on Sit"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnSit(this); + } + + public final void cancelOnTakeDamage() { + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.cancelOnTakeDamage() && eff.cancel()) { + //System.out.println("canceling on Take Damage"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnTakeDamage(this); + } + + public final void cancelOnTakeDamage(final DamageType type, final float amount) { + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.cancelOnTakeDamage(type, amount) && eff.cancel()) { + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + } + + public final Effect getDamageAbsorber() { + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.isDamageAbsorber()) { + return eff; + } + } + return null; + } + + public final void cancelOnUnEquip() { + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + if (eff == null) + continue; + if (eff.cancelOnUnEquip() && eff.cancel()) { + //System.out.println("canceling on UnEquip"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnUnEquip(this); + } + + public final void cancelOnStun() { + boolean changed = false; + for (String s : this.effects.keySet()) { + Effect eff = this.effects.get(s); + + if (eff == null) { + Logger.error("null effect for " + this.getObjectUUID() + " : effect " + s); + continue; + } + if (eff.cancelOnStun() && eff.cancel()) { + //System.out.println("canceling on Stun"); + eff.cancelJob(); + this.effects.remove(s); + changed = true; + } + } + if (changed) { + applyBonuses(); + } + PowersManager.cancelOnStun(this); + } + + //Call to apply any new effects to player + public synchronized void applyBonuses() { + PlayerCharacter player; + //tell the player to applyBonuses because something has changed + + //start running the bonus calculations + + try { + runBonuses(); + + // Check if calculations affected flight. + + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { + player = (PlayerCharacter) this; + + // Ground players who cannot fly but are currently flying + + if (CanFly(player) == false && + player.getMovementState().equals(MovementState.FLYING)) + PlayerCharacter.GroundPlayer(player); + } + + } catch (Exception e) { + Logger.error("Error in run bonuses for object UUID " + this.getObjectUUID()); + Logger.error(e); + } + } + + //Don't call this function directly. linked from ac.applyBonuses() + //through BonusCalcJob. Designed to only run from one worker thread + public final void runBonuses() { + // synchronized with getBonuses() + synchronized (this.bonuses) { + try { + //run until no new bonuses are applied + + // clear bonuses and reapply rune bonuses + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { + this.bonuses.calculateRuneBaseEffects((PlayerCharacter) this); + } else { + this.bonuses.clearRuneBaseEffects(); + } + + // apply effect bonuses + for (Effect eff : this.effects.values()) { + eff.applyBonus(this); + } + + //apply item bonuses for equipped items + ConcurrentHashMap equip = null; + + if (this.charItemManager != null) { + equip = this.charItemManager.getEquipped(); + } + if (equip != null) { + for (Item item : equip.values()) { + item.clearBonuses(); + if (item != null) { + ConcurrentHashMap effects = item.getEffects(); + if (effects != null) { + for (Effect eff : effects.values()) { + eff.applyBonus(item, this); + } + } + } + } + } + + //recalculate passive defenses + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { + ((PlayerCharacter) this).setPassives(); + } + + + // recalculate everything + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) this; + + //calculate item bonuses + pc.calculateItemBonuses(); + + //recalculate formulas + pc.recalculatePlayerStats(true); + + + } else if (this.getObjectType().equals(GameObjectType.Mob)) { + Mob mob = (Mob) this; + + //recalculate formulas + mob.recalculateStats(); + } + } catch (Exception e) { + Logger.error(e); + } + } + } + + public int getInBuildingID() { + return inBuildingID; + } + + public void setInBuildingID(int inBuildingID) { + this.inBuildingID = inBuildingID; + } + + public float getHateValue() { + if (this.hateValue <= 0) { + this.hateValue = 0; + return hateValue; + } + + if (this.lastHateUpdate == 0) { + this.lastHateUpdate = System.currentTimeMillis(); + return this.hateValue; + } + long duration = System.currentTimeMillis() - this.lastHateUpdate; + //convert duration to seconds and multiply Hate Delimiter. + float modAmount = duration / 1000 * MBServerStatics.PLAYER_HATE_DELIMITER; + this.hateValue -= modAmount; + this.lastHateUpdate = System.currentTimeMillis(); + return this.hateValue; + } + + public void setHateValue(float hateValue) { + this.lastHateUpdate = System.currentTimeMillis(); + this.hateValue = hateValue; + } + + public int getInFloorID() { + return inFloorID; + } + + public void setInFloorID(int inFloorID) { + this.inFloorID = inFloorID; + } + + public float getDesiredAltitude() { + return desiredAltitude; + } + + public void setDesiredAltitude(float desiredAltitude) { + this.desiredAltitude = desiredAltitude; + } + + public long getTakeOffTime() { + return takeOffTime; + } + + public void setTakeOffTime(long takeOffTime) { + this.takeOffTime = takeOffTime; + } + + public boolean isItemCasting() { + return itemCasting; + } + + public void setItemCasting(boolean itemCasting) { + this.itemCasting = itemCasting; + } + + //updates + public void update() { + } + + public void updateRegen() { + } + + public void updateMovementState() { + } + + public void updateLocation() { + } + + public void updateFlight() { + } + + public void dynamicUpdate(UpdateType updateType) { + if (this.updateLock.writeLock().tryLock()) { + try { + switch (updateType) { + case ALL: + update(); + break; + case REGEN: + updateRegen(); + break; + case LOCATION: + update(); + break; + case MOVEMENTSTATE: + update(); + break; + case FLIGHT: + updateFlight(); + break; + } + + } catch (Exception e) { + Logger.error(e); + } finally { + this.updateLock.writeLock().unlock(); + } + } + + } + + public boolean isMovingUp() { + return movingUp; + } + + public void setMovingUp(boolean movingUp) { + this.movingUp = movingUp; + } +} diff --git a/src/engine/objects/AbstractGameObject.java b/src/engine/objects/AbstractGameObject.java new file mode 100644 index 00000000..b105cf14 --- /dev/null +++ b/src/engine/objects/AbstractGameObject.java @@ -0,0 +1,233 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.GameObjectType; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.DatabaseUpdateJob; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; + + +public abstract class AbstractGameObject { + private GameObjectType objectType = GameObjectType.unknown; + private int objectUUID; + + private byte ver = 1; + + private ConcurrentHashMap databaseJobs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + /** + * No Table ID Constructor + */ + public AbstractGameObject() { + super(); + setObjectType(); + this.objectUUID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET; + } + + /** + * Normal Constructor + */ + public AbstractGameObject(int objectUUID) { + this(); + this.objectUUID = objectUUID; + } + + /** + * ResultSet Constructor + * + * @param rs ResultSet containing record for this object + */ + public AbstractGameObject(ResultSet rs, int objectUUID) throws SQLException { + this(); + this.objectUUID = objectUUID; + } + + /** + * ResultSet Constructor; assumes first column in ResultSet is ID + * + * @param rs ResultSet containing record for this object + */ + public AbstractGameObject(ResultSet rs) throws SQLException { + this(rs, rs.getInt(1)); + } + + public static int extractUUID(GameObjectType type, long compositeID) { + if (type == null || type == GameObjectType.unknown || compositeID == 0L) { + return -1; + } + int out = (int) compositeID; + if (out > Long.MAX_VALUE || out < 0) { + Logger.error("There was a problem reverse calculating a UUID from a compositeID. \tcomposID: " + + compositeID + " \ttype: " + type.toString() + "\tresult: " + out); + } + return out; + } + + public static GameObjectType extractTypeID(long compositeID) { + int ordinal = (int) (compositeID >>> 32); + return GameObjectType.values()[ordinal]; + } + + /** + * Generates a {@link PreparedStatementShared} based on the specified query. + *

+ * If {@link AbstractGameObject} Database functions will properly release + * the PreparedStatementShared upon completion. If these functions are not + * used, then {@link PreparedStatementShared#release release()} must be + * called when finished with this object. + * + * @param sql The SQL string used to generate the PreparedStatementShared + * @return {@link PreparedStatementShared} + * @throws {@link SQLException} + **/ + protected static PreparedStatementShared prepareStatement(String sql) throws SQLException { + return new PreparedStatementShared(sql); + } + + public static AbstractGameObject getFromTypeAndID(long compositeID) { + int objectTypeID = extractTypeOrdinal(compositeID); + int tableID = extractTableID(objectTypeID, compositeID); + GameObjectType objectType = GameObjectType.values()[objectTypeID]; + + switch (objectType) { + case PlayerCharacter: + return PlayerCharacter.getPlayerCharacter(tableID); + + case NPC: + return NPC.getNPC(tableID); + + case Mob: + return Mob.getMob(tableID); + + case Building: + return BuildingManager.getBuilding(tableID); + + case Guild: + return Guild.getGuild(tableID); + + case Item: + return Item.getFromCache(tableID); + + case MobLoot: + return MobLoot.getFromCache(tableID); + + default: + Logger.error("Failed to convert compositeID to AbstractGameObject. " + "Unsupported type encountered. " + + "CompositeID: " + compositeID + " ObjectType: 0x" + Integer.toHexString(objectTypeID) + " TableID: " + tableID); + } + return null; + } + + public static int extractTypeOrdinal(long compositeID) { + return (int) (compositeID >>> 32); + } + + public static int extractTableID(int type, long compositeID) { + if (type == 0 || compositeID == 0L) { + return -1; + } + return (int) compositeID; + } + + /* + * Util + */ + + /* + * Getters + */ + public GameObjectType getObjectType() { + return this.objectType; + } + + protected final void setObjectType() { + try { + this.objectType = GameObjectType.valueOf(this.getClass().getSimpleName()); + } catch (SecurityException | IllegalArgumentException e) { + Logger.error("Failed to find class " + this.getClass().getSimpleName() + + " in GameObjectTypes file. Defaulting ObjectType to 0."); + } + } + + public int getObjectUUID() { + return this.objectUUID; + } + + protected void setObjectUUID(int objectUUID) { + this.objectUUID = objectUUID; + } + + public byte getVer() { + return this.ver; + } + + public void incVer() { + this.ver++; + if (this.ver == (byte) -1) //-1 reserved + this.ver++; + } + + public boolean equals(AbstractGameObject obj) { + + if (obj == null) + return false; + + if (obj.objectType != this.objectType) { + return false; + } + + return obj.getObjectUUID() == this.getObjectUUID(); + } + + public void removeFromCache() { + DbManager.removeFromCache(this); + } + + public ConcurrentHashMap getDatabaseJobs() { + return this.databaseJobs; + } + + public void addDatabaseJob(String type, int duration) { + DatabaseUpdateJob updateJob; + + if (databaseJobs.containsKey(type)) + return; + + updateJob = new DatabaseUpdateJob(this, type); + JobContainer jc = JobScheduler.getInstance().scheduleJob(updateJob, duration); + databaseJobs.put(type, jc); + } + + public void removeDatabaseJob(String type, boolean canceled) { + if (databaseJobs.containsKey(type)) { + if (canceled) { + JobContainer jc = databaseJobs.get(type); + if (jc != null) + jc.cancelJob(); + } + databaseJobs.remove(type); + } + } + + /* + * Abstract Methods + */ + + public abstract void updateDatabase(); +} diff --git a/src/engine/objects/AbstractIntelligenceAgent.java b/src/engine/objects/AbstractIntelligenceAgent.java new file mode 100644 index 00000000..580a3094 --- /dev/null +++ b/src/engine/objects/AbstractIntelligenceAgent.java @@ -0,0 +1,185 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.InterestManagement.WorldGrid; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.mobileAI.Threads.MobAIThread; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.PetMsg; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + + +public abstract class AbstractIntelligenceAgent extends AbstractCharacter { + protected Vector3fImmutable lastBindLoc; + public boolean assist = false; + public Enum.AIAgentType agentType = Enum.AIAgentType.MOBILE; + + + public AbstractIntelligenceAgent(ResultSet rs) throws SQLException { + super(rs); + } + + public AbstractIntelligenceAgent(String firstName, + String lastName, short statStrCurrent, short statDexCurrent, + short statConCurrent, short statIntCurrent, short statSpiCurrent, + short level, int exp, boolean sit, boolean walk, boolean combat, + Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir, + short healthCurrent, short manaCurrent, short stamCurrent, + Guild guild, byte runningTrains) { + super(firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent, + statIntCurrent, statSpiCurrent, level, exp, bindLoc, + faceDir, guild, + runningTrains); + } + + public AbstractIntelligenceAgent(String firstName, + String lastName, short statStrCurrent, short statDexCurrent, + short statConCurrent, short statIntCurrent, short statSpiCurrent, + short level, int exp, boolean sit, boolean walk, boolean combat, + Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir, + short healthCurrent, short manaCurrent, short stamCurrent, + Guild guild, byte runningTrains, int newUUID) { + super(firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent, + statIntCurrent, statSpiCurrent, level, exp, bindLoc, + currentLoc, faceDir, guild, + runningTrains, newUUID); + } + + @Override + public void setObjectTypeMask(int mask) { + mask |= MBServerStatics.MASK_IAGENT; + super.setObjectTypeMask(mask); + } + + /* AI Job Management */ + + public MobBase getMobBase() { + + if (this.getObjectType().equals(GameObjectType.Mob)) + return this.getMobBase(); + + return null; + } + + public void setPet(PlayerCharacter owner, boolean summoned) { + + if (summoned) + this.agentType = Enum.AIAgentType.PET; //summoned + else + this.agentType = Enum.AIAgentType.CHARMED; + + if (this.getObjectType().equals(GameObjectType.Mob)) { + ((Mob) this).setOwner(owner); + } + } + + + public boolean isPet() { + + return (this.agentType.equals(Enum.AIAgentType.PET) || + this.agentType.equals(Enum.AIAgentType.CHARMED)); + } + + public void toggleAssist() { + this.assist = !this.assist; + } + + public int getDBID() { + + if (this.getObjectType().equals(GameObjectType.Mob)) + return this.getDBID(); + + return 0; + } + + public PlayerCharacter getOwner() { + + if (this.getObjectType().equals(GameObjectType.Mob)) + return this.getOwner(); + + return null; + } + + public boolean getSafeZone() { + + ArrayList allIn = ZoneManager.getAllZonesIn(this.getLoc()); + + for (Zone zone : allIn) + if (zone.getSafeZone() == (byte) 1) + return true; + + return false; + } + + public float getAggroRange() { + + float ret = MobAIThread.AI_BASE_AGGRO_RANGE; + + if (this.bonuses != null) + ret *= (1 + this.bonuses.getFloatPercentAll(ModType.ScanRange, SourceType.None)); + + return ret; + } + + public void dismiss() { + + if (this.isPet()) { + + if ((this.agentType.equals(Enum.AIAgentType.PET))) { //delete summoned pet + + WorldGrid.RemoveWorldObject(this); + + if (this.getObjectType() == GameObjectType.Mob) + if (((Mob) this).getParentZone() != null) + ((Mob) this).getParentZone().zoneMobSet.remove(this); + + } else { //revert charmed pet + this.agentType = Enum.AIAgentType.MOBILE; + this.setCombatTarget(null); + } + + //clear owner + + PlayerCharacter owner = this.getOwner(); + + //close pet window + + if (owner != null) { + + Mob pet = owner.getPet(); + + PetMsg petMsg = new PetMsg(5, null); + Dispatch dispatch = Dispatch.borrow(owner, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + if (pet != null && pet.getObjectUUID() == this.getObjectUUID()) + owner.setPet(null); + + if (this.getObjectType().equals(GameObjectType.Mob)) + ((Mob) this).setOwner(null); + } + + + } + } + +} + diff --git a/src/engine/objects/AbstractWorldObject.java b/src/engine/objects/AbstractWorldObject.java new file mode 100644 index 00000000..97d8a5d1 --- /dev/null +++ b/src/engine/objects/AbstractWorldObject.java @@ -0,0 +1,625 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.DispatchChannel; +import engine.Enum.EffectSourceType; +import engine.Enum.GameObjectType; +import engine.Enum.GridObjectType; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.WorldGrid; +import engine.job.AbstractScheduleJob; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.NoTimeJob; +import engine.math.AtomicFloat; +import engine.math.Bounds; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.UpdateEffectsMsg; +import engine.powers.EffectsBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public abstract class AbstractWorldObject extends AbstractGameObject { + + protected final ReadWriteLock locationLock = new ReentrantReadWriteLock(true); + protected final ReadWriteLock updateLock = new ReentrantReadWriteLock(true); + public float healthMax; + public ConcurrentHashMap effects = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public int gridX = -1; + public int gridZ = -1; + public Regions region; + public Regions landingRegion = null; + public Vector3fImmutable lastLoc = Vector3fImmutable.ZERO; + public Vector3fImmutable loc = new Vector3fImmutable(0.0f, 0.0f, 0.0f); + protected AtomicFloat health = new AtomicFloat(); + protected boolean load = true; + protected GridObjectType gridObjectType; + protected float altitude = 0; + protected boolean movingUp = false; + private String name = ""; + private byte tier = 0; + private Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f); + private int objectTypeMask = 0; + private Bounds bounds; + + /** + * No Id Constructor + */ + public AbstractWorldObject() { + super(); + } + + /** + * Normal Constructor + */ + public AbstractWorldObject(int objectUUID) { + super(objectUUID); + } + + + /** + * ResultSet Constructor + */ + public AbstractWorldObject(ResultSet rs) throws SQLException { + super(rs); + } + + public static int getType() { + return 0; + } + + public static boolean IsAbstractCharacter(AbstractWorldObject awo) { + + if (awo == null) + return false; + + if (awo.getObjectType() == GameObjectType.PlayerCharacter || awo.getObjectType() == GameObjectType.Mob || awo.getObjectType() == GameObjectType.NPC) + return true; + return false; + } + + public static void RemoveFromWorldGrid(AbstractWorldObject gridObjectToRemove) { + if (gridObjectToRemove.gridX < 0 || gridObjectToRemove.gridZ < 0) + return; + + ConcurrentHashMap gridMap; + switch (gridObjectToRemove.gridObjectType) { + case STATIC: + gridMap = WorldGrid.StaticGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ]; + break; + case DYNAMIC: + gridMap = WorldGrid.DynamicGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ]; + break; + default: + gridMap = WorldGrid.StaticGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ]; + break; + + } + + if (gridMap == null) { + Logger.info("Null gridmap for Object UUD: " + gridObjectToRemove); + return; + } + + + gridMap.remove(gridObjectToRemove.getObjectUUID()); + gridObjectToRemove.gridX = -1; + gridObjectToRemove.gridZ = -1; + + } + + public static boolean AddToWorldGrid(AbstractWorldObject gridObjectToAdd, int x, int z) { + try { + + ConcurrentHashMap gridMap; + if (gridObjectToAdd.gridObjectType.equals(GridObjectType.STATIC)) + gridMap = WorldGrid.StaticGridMap[x][z]; + else + gridMap = WorldGrid.DynamicGridMap[x][z]; + + gridMap.put(gridObjectToAdd.getObjectUUID(), gridObjectToAdd); + gridObjectToAdd.gridX = x; + gridObjectToAdd.gridZ = z; + return true; + } catch (Exception e) { + Logger.error(e); + return false; + } + + } + + public static Regions GetRegionByWorldObject(AbstractWorldObject worldObject) { + Regions region = null; + + if (worldObject.getObjectType().equals(GameObjectType.PlayerCharacter)) + if (((PlayerCharacter) worldObject).isFlying()) + return null; + //Find building + for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(worldObject.getLoc(), MBServerStatics.STRUCTURE_LOAD_RANGE, MBServerStatics.MASK_BUILDING)) { + Building building = (Building) awo; + if (!Bounds.collide(worldObject.getLoc(), building.getBounds())) + continue; + + //find regions that intersect x and z, check if object can enter. + for (Regions toEnter : building.getBounds().getRegions()) { + if (toEnter.isPointInPolygon(worldObject.getLoc())) { + if (Regions.CanEnterRegion(worldObject, toEnter)) + if (region == null) + region = toEnter; + else // we're using a low level to high level tree structure, database not always in order low to high. + //check for highest level index. + if (region != null && toEnter.highLerp.y > region.highLerp.y) + region = toEnter; + + + } + } + } + + //set players new altitude to region lerp altitude. + if (region != null) + if (region.center.y == region.highLerp.y) + worldObject.loc = worldObject.loc.setY(region.center.y + worldObject.getAltitude()); + else + worldObject.loc = worldObject.loc.setY(region.lerpY(worldObject) + worldObject.getAltitude()); + + return region; + } + + public static Regions GetRegionFromBuilding(Vector3fImmutable worldLoc, Building building) { + Regions region = null; + + + return region; + } + + //this should be called to handle any after load functions. + public abstract void runAfterLoad(); + + /* + * Getters + */ + public float getHealth() { + + + return this.health.get(); + + } + + /** + * @param health the health to set + */ + public void setHealth(float health) { + + this.health.set(health); + } + + public float getCurrentHitpoints() { + return this.health.get(); + } + + public float getHealthMax() { + return this.healthMax; + } + + public ConcurrentHashMap getEffects() { + return this.effects; + } + + //Add new effect + public void addEffect(String name, int duration, AbstractScheduleJob asj, EffectsBase eb, int trains) { + + if (!isAlive() && eb.getToken() != 1672601862) { + return; + } + JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration); + Effect eff = new Effect(jc, eb, trains); + this.effects.put(name, eff); + applyAllBonuses(); + } + + public Effect addEffectNoTimer(String name, EffectsBase eb, int trains, boolean isStatic) { + NoTimeJob ntj = new NoTimeJob(this, name, eb, trains); //infinite timer + + if (this.getObjectType() == GameObjectType.Item || this.getObjectType() == GameObjectType.City) { + ntj.setEffectSourceType(this.getObjectType().ordinal()); + ntj.setEffectSourceID(this.getObjectUUID()); + } + + JobContainer jc = new JobContainer(ntj); + Effect eff = new Effect(jc, eb, trains); + if (isStatic) + eff.setIsStatic(isStatic); + this.effects.put(name, eff); + applyAllBonuses(); + return eff; + } + + //called when an effect runs it's course + public void endEffect(String name) { + + Effect eff = this.effects.get(name); + if (eff == null) { + return; + } + if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) { + return; + } + + if (eff.cancel()) { + + eff.endEffect(); + this.effects.remove(name); + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) + if (name.equals("Flight")) { + ((PlayerCharacter) this).update(); + PlayerCharacter.GroundPlayer((PlayerCharacter) this); + } + } + applyAllBonuses(); + } + + public void endEffectNoPower(String name) { + + Effect eff = this.effects.get(name); + if (eff == null) { + return; + } + if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) { + return; + } + + if (eff.cancel()) { + eff.cancelJob(); + eff.endEffectNoPower(); + this.effects.remove(name); + } + applyAllBonuses(); + } + + //Called to cancel an effect prematurely. + public void cancelEffect(String name, boolean overwrite) { + + Effect eff = this.effects.get(name); + if (eff == null) { + return; + } + if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) { + return; + } + + if (eff.cancel()) { + eff.cancelJob(); + this.effects.remove(name); + if (AbstractWorldObject.IsAbstractCharacter(this)) { + ((AbstractCharacter) this).cancelLastChantIfSame(eff); + } + } + if (!overwrite) { + applyAllBonuses(); + } + } + + //Called when an object dies/is destroyed + public void clearEffects() { + for (String name : this.effects.keySet()) { + Effect eff = this.effects.get(name); + if (eff == null) { + return; + } + + //Dont remove deathshroud here! + if (eff.getEffectToken() == 1672601862) + continue; + + if (eff.cancel()) { + if (eff.getPower() == null) { + if (!eff.isStatic()) + eff.endEffectNoPower(); + } + if (!eff.isStatic()) + eff.cancelJob(); + } + + this.effects.remove(name); + } + if (AbstractWorldObject.IsAbstractCharacter(this)) { + ((AbstractCharacter) this).cancelLastChant(); + } + applyAllBonuses(); + } + + public void removeEffectBySource(EffectSourceType source, int trains, boolean removeAll) { + if (!isAlive() && source.equals(EffectSourceType.DeathShroud) == false) { + return; + } + + //hacky way to dispell trebs. + if (this.getObjectType() == GameObjectType.Mob) { + Mob mob = (Mob) this; + if (mob.isSiege()) { + if (mob.isPet()) { + PlayerCharacter petOwner = mob.getOwner(); + if (petOwner != null && source.equals(EffectSourceType.Effect)) { + petOwner.dismissPet(); + return; + } + } + } + } + boolean changed = false; + String toRemove = ""; + int toRemoveToken = Integer.MAX_VALUE; + for (String name : this.effects.keySet()) { + Effect eff = this.effects.get(name); + if (eff == null) { + continue; + } + if (eff.containsSource(source) && trains >= eff.getTrains()) { + if (removeAll) { + //remove all effects of source type + if (eff.cancel()) { + eff.cancelJob(); + } + this.effects.remove(name); + changed = true; + + if (source.equals("Flight")) { + //ground player + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { + ((PlayerCharacter) this).update(); + PlayerCharacter.GroundPlayer((PlayerCharacter) this); + } + } + } else { + //find lowest token of source type to remove + int tok = eff.getEffectToken(); + if (tok != 0 && tok < toRemoveToken) { + toRemove = name; + toRemoveToken = tok; + } + } + } + } + + //WTF IS THIS? + if (toRemoveToken < Integer.MAX_VALUE && this.effects.containsKey(toRemove)) { + //remove lowest found token of source type + Effect eff = this.effects.get(toRemove); + if (eff != null) { + changed = true; + if (eff.cancel()) { + eff.cancelJob(); + } + this.effects.remove(toRemove); + + if (source.equals("Flight")) { + //ground player + if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { + ((PlayerCharacter) this).update(); + PlayerCharacter.GroundPlayer((PlayerCharacter) this); + } + } + + } + } + if (changed) { + applyAllBonuses(); + } + } + + public void sendAllEffects(ClientConnection cc) { + UpdateEffectsMsg msg = new UpdateEffectsMsg(this); + Dispatch dispatch = Dispatch.borrow((PlayerCharacter) this, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + public void applyAllBonuses() { + if (AbstractWorldObject.IsAbstractCharacter(this)) { + ((AbstractCharacter) this).applyBonuses(); + } + } + + public JobContainer getEffectJobContainer(String name) { + Effect ef = this.effects.get(name); + if (ef != null) { + return ef.getJobContainer(); + } + return null; + } + + public AbstractScheduleJob getEffectJob(String name) { + Effect ef = this.effects.get(name); + if (ef == null) { + return null; + } + JobContainer jc = ef.getJobContainer(); + if (jc != null) { + return (AbstractScheduleJob) jc.getJob(); + } + return null; + } + + public boolean containsEffect(int token) { + for (Effect eff : this.effects.values()) { + if (eff != null) { + if (eff.getEffectsBase() != null) { + if (eff.getEffectsBase().getToken() == token) { + return true; + } + } + } + } + return false; + } + + public int getObjectTypeMask() { + return objectTypeMask; + } + + /* + * Setters + */ + public void setObjectTypeMask(int mask) { + this.objectTypeMask = mask; + } + + public Vector3fImmutable getLoc() { + return this.loc; + } + + //TODO return false if something goes wrong? resync player? + public void setLoc(Vector3fImmutable loc) { + locationLock.writeLock().lock(); + try { + if (Float.isNaN(loc.x) || Float.isNaN(loc.z)) + return; + + if (loc.equals(Vector3fImmutable.ZERO)) + return; + + if (loc.x > MBServerStatics.MAX_WORLD_WIDTH || loc.z < MBServerStatics.MAX_WORLD_HEIGHT) + return; + this.lastLoc = new Vector3fImmutable(this.loc); + this.loc = loc; + this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude()); + + //lets not add mob to world grid if he is currently despawned. + if (this.getObjectType().equals(GameObjectType.Mob) && ((Mob) this).despawned) + return; + + //do not add objectUUID 0 to world grid. dunno da fuck this doing why its doing but its doing... da fuck. + if (this.getObjectUUID() == 0) + return; + WorldGrid.addObject(this, loc.x, loc.z); + + } catch (Exception e) { + Logger.error("Failed to set location for World Object. Type = " + this.getObjectType().name() + " : Name = " + this.getName()); + e.printStackTrace(); + } finally { + locationLock.writeLock().unlock(); + } + + } + + public Vector3f getRot() { + return rot; + } + + public void setRot(Vector3f rotation) { + synchronized (this.rot) { + this.rot = rotation; + } + } + + public byte getTier() { + return tier; + } + + public void setTier(byte tier) { + synchronized (this.rot) { + this.tier = tier; + } + } + + public boolean isAlive() { + if (AbstractWorldObject.IsAbstractCharacter(this)) { + return this.isAlive(); + } else if (this.getObjectType().equals(GameObjectType.Building)) { + return (!(((Building) this).getRank() < 0)); + } else { + return true; + } + } + + public void setY(float y) { + this.loc = this.loc.setY(y); + } + + public boolean load() { + return this.load; + } + + /* + * Utils + */ + public String getName() { + if (this.name.length() == 0) { + return "Unnamed " + '(' + + this.getObjectUUID() + ')'; + } else { + return this.name; + } + } + + public String getSimpleName() { + return this.name; + } + + /** + * @return the bounds + */ + public Bounds getBounds() { + return bounds; + } + + public void setBounds(Bounds bounds) { + + this.bounds = bounds; + } + + public float getAltitude() { + return altitude; + } + + + public ReadWriteLock getUpdateLock() { + return updateLock; + } + + public GridObjectType getGridObjectType() { + return gridObjectType; + } + + + public boolean isMovingUp() { + return movingUp; + } + + public void setMovingUp(boolean movingUp) { + this.movingUp = movingUp; + } + + public void setRegion(Regions region) { + this.region = region; + } + + //used for interestmanager loading and unloading objects to client. + // if not in grid, unload from player. + public boolean isInWorldGrid() { + if (this.gridX == -1 && this.gridZ == -1) + return false; + + return true; + } + + +} diff --git a/src/engine/objects/Account.java b/src/engine/objects/Account.java new file mode 100644 index 00000000..7beb72b5 --- /dev/null +++ b/src/engine/objects/Account.java @@ -0,0 +1,390 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.Enum.ItemContainerType; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.ClientMessagePump; +import engine.net.client.msg.*; +import engine.util.ByteUtils; +import org.pmw.tinylog.Logger; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + +public class Account extends AbstractGameObject { + + public static ConcurrentHashMap AccountsMap = new ConcurrentHashMap<>(); + private final String uname; + public String discordAccount; + public HashMap characterMap = new HashMap<>(); + public Item vaultGold = null; + public long lastPasswordCheck = 0; + public Enum.AccountStatus status; + private String passwd; + private int lastCharIDUsed; + private String salt; + private byte loginAttempts = 0; + private long lastLoginFailure = System.currentTimeMillis(); + private ArrayList vault = new ArrayList<>(); + + public Account(ResultSet resultSet) throws SQLException { + super(resultSet); + + this.uname = resultSet.getString("acct_uname"); + this.passwd = resultSet.getString("acct_passwd"); + this.lastCharIDUsed = resultSet.getInt("acct_lastCharUID"); + this.salt = resultSet.getString("acct_salt"); + this.discordAccount = resultSet.getString("discordAccount"); + this.status = Enum.AccountStatus.valueOf(resultSet.getString("status")); + } + + public ArrayList getVault() { + return vault; + } + + public String getUname() { + return uname; + } + + public String getPasswd() { + return passwd; + } + + public String getSalt() { + return salt; + } + + public int getLastCharIDUsed() { + return lastCharIDUsed; + } + + public void setLastCharIDUsed(int lastCharIDUsed) { + this.lastCharIDUsed = lastCharIDUsed; + } + + public byte getLoginAttempts() { + return loginAttempts; + } + + public long getLastLoginFailure() { + return this.lastLoginFailure; + } + + public void setLastLoginFailure() { + this.lastLoginFailure = System.currentTimeMillis(); + } + + public void setLastCharacter(int uuid) { + this.lastCharIDUsed = uuid; + // this.updateDatabase(); + } + + public void incrementLoginAttempts() { + ++this.loginAttempts; + this.setLastLoginFailure(); + } + + public void resetLoginAttempts() { + this.loginAttempts = 0; + } + + /* + * on successfully matching the password, this method additionally calls to + * associateIpToAccount for IPAddress tracking. dokks + */ + public boolean passIsValid(String pw, String ip, String machineID) throws IllegalArgumentException { + boolean result = false; + // see if it was entered in plain text first, if the plain text matches, + // hash it and save to the database. + try { + pw = ByteUtils.byteArrayToSafeStringHex(MessageDigest + .getInstance("md5").digest(pw.getBytes("UTF-8"))) + + salt; + pw = ByteUtils.byteArrayToSafeStringHex(MessageDigest + .getInstance("md5").digest(pw.getBytes())); + result = this.passwd.equals(pw); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + Logger.error(e.toString()); + } + + if (result) { + // TODO: should use an executor here so that we can + // fire and forget this update. + // this is a valid user, so let's also update the + // database with login time and IP. + if ((ip == null) || (ip.length() == 0)) { + throw new IllegalArgumentException(); + } + } + return result; + } + + public ClientConnection getClientConnection() { + return SessionManager.getClientConnection(this); + } + + public PlayerCharacter getPlayerCharacter() { + return SessionManager.getPlayerCharacter(this); + } + + @Override + public void updateDatabase() { + DbManager.AccountQueries.updateDatabase(this); + } + + //this should be called to handle any after load functions. + + public void runAfterLoad() { + + try { + + if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) { + ArrayList playerList = DbManager.PlayerCharacterQueries.GET_CHARACTERS_FOR_ACCOUNT(this.getObjectUUID()); + + for (PlayerCharacter player : playerList) { + PlayerCharacter.initializePlayer(player); + this.characterMap.putIfAbsent(player.getObjectUUID(), player); + } + + playerList.clear(); + } + + if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER)) { + this.vault = DbManager.ItemQueries.GET_ITEMS_FOR_ACCOUNT(this.getObjectUUID()); + + for (Item item : this.vault) { + if (item.getItemBase().getUUID() == 7) { + this.vaultGold = item; + } + } + + if (this.vaultGold == null) { + this.vaultGold = Item.newGoldItem(this.getObjectUUID(), ItemBase.getItemBase(7), ItemContainerType.VAULT); + + if (this.vaultGold != null) + this.vault.add(this.vaultGold); + } + } + + } catch (Exception e) { + Logger.error(e); + } + } + + public synchronized void transferItemFromInventoryToVault(TransferItemFromInventoryToVaultMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault")) { + ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "You are out of range of the vault."); + return; + } + + int uuid = msg.getUUID(); + Item item = Item.getFromCache(uuid); + + if (item == null) { + ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "Can't find the item."); + return; + } + + //dupe check + if (!item.validForInventory(origin, player, player.getCharItemManager())) + return; + + if (item.containerType == Enum.ItemContainerType.INVENTORY && player.getCharItemManager().isVaultOpen()) { + if (!player.getCharItemManager().hasRoomVault(item.getItemBase().getWeight())) { + ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "There is no room in your vault."); + return; + } + + if (player.getCharItemManager().moveItemToVault(item)) { + this.vault.add(item); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } else + ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "Failed to transfer item."); + } + } + + public synchronized void transferItemFromVaultToInventory(TransferItemFromVaultToInventoryMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault")) { + ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "You are out of range of the vault."); + return; + } + + CharacterItemManager itemManager = player.getCharItemManager(); + + if (itemManager == null) { + ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "Can't find your item manager."); + return; + } + + Item item = Item.getFromCache(msg.getUUID()); + + if (item == null) { + ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "Can't find the item."); + return; + } + + //dupe check + if (!item.validForVault(origin, player, itemManager)) + return; + + if (item.containerType == Enum.ItemContainerType.VAULT && itemManager.isVaultOpen()) { + if (!itemManager.hasRoomInventory(item.getItemBase().getWeight())) { + ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "There is no room in your inventory."); + return; + } + if (itemManager.moveItemToInventory(item)) { + this.vault.remove(item); + + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } else + ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "Failed to transfer item."); + } + } + + public synchronized void transferGoldFromVaultToInventory(TransferGoldFromVaultToInventoryMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + Account account = player.getAccount(); + + if (account == null) + return; + + if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault")) + return; + + NPC npc = player.getLastNPCDialog(); + + if (npc == null) + return; + + CharacterItemManager itemManager = player.getCharItemManager(); + + if (itemManager == null) + return; + + if (itemManager.isVaultOpen() == false) + return; + + if (itemManager.moveGoldToInventory(itemManager.getGoldVault(), msg.getAmount()) == false) + return; + + OpenVaultMsg open = new OpenVaultMsg(player, npc); + ShowVaultInventoryMsg show = new ShowVaultInventoryMsg(player, account, npc); // 37?? + + UpdateGoldMsg ugm = new UpdateGoldMsg(player); + ugm.configure(); + dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + UpdateVaultMsg uvm = new UpdateVaultMsg(account); + dispatch = Dispatch.borrow(player, uvm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + dispatch = Dispatch.borrow(player, open); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + dispatch = Dispatch.borrow(player, show); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } + + public synchronized void transferGoldFromInventoryToVault(TransferGoldFromInventoryToVaultMsg msg, ClientConnection origin) { + + PlayerCharacter player = origin.getPlayerCharacter(); + Dispatch dispatch; + + if (player == null) + return; + + Account account = player.getAccount(); + + if (account == null) + return; + + if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault")) + return; + + CharacterItemManager itemManager = player.getCharItemManager(); + + if (itemManager == null) + return; + + NPC npc = player.getLastNPCDialog(); + + if (npc == null) + return; + + // Cannot have bank and vault open concurrently + // Dupe prevention + + if (itemManager.isVaultOpen() == false) + return; + + // Something went horribly wrong. Should be log this? + + if (itemManager.moveGoldToVault(itemManager.getGoldInventory(), msg.getAmount()) == false) + return; + + UpdateGoldMsg ugm = new UpdateGoldMsg(player); + ugm.configure(); + dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + UpdateVaultMsg uvm = new UpdateVaultMsg(account); + dispatch = Dispatch.borrow(player, uvm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + OpenVaultMsg open = new OpenVaultMsg(player, npc); + dispatch = Dispatch.borrow(player, open); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + // + // + ShowVaultInventoryMsg show = new ShowVaultInventoryMsg(player, account, npc); // 37?? + dispatch = Dispatch.borrow(player, show); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } +} diff --git a/src/engine/objects/Bane.java b/src/engine/objects/Bane.java new file mode 100644 index 00000000..5b98aaa4 --- /dev/null +++ b/src/engine/objects/Bane.java @@ -0,0 +1,647 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.ProtectionState; +import engine.Enum.SiegePhase; +import engine.Enum.SiegeResult; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.BaneRecord; +import engine.db.archive.DataWarehouse; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.job.JobScheduler; +import engine.jobs.ActivateBaneJob; +import engine.jobs.BaneDefaultTimeJob; +import engine.math.Vector3fImmutable; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.PlaceAssetMsg; +import engine.net.client.msg.chat.ChatSystemMsg; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; + +public final class Bane { + + public static ConcurrentHashMap banes = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final int cityUUID; + private final int stoneUUID; + private int ownerUUID; + private DateTime placementDate = null; + private DateTime liveDate = null; + private BaneDefaultTimeJob defaultTimeJob; + + // Internal cache for banes + private ActivateBaneJob activateBaneJob; + + /** + * ResultSet Constructor + */ + public Bane(ResultSet rs) throws SQLException { + + Date sqlDateTime; + ActivateBaneJob abtj; + + this.cityUUID = rs.getInt("cityUUID"); + this.ownerUUID = rs.getInt("ownerUUID"); + this.stoneUUID = rs.getInt("stoneUUID"); + + sqlDateTime = rs.getTimestamp("placementDate"); + + if (sqlDateTime != null) + this.placementDate = new DateTime(sqlDateTime); + + sqlDateTime = rs.getTimestamp("liveDate"); + + if (sqlDateTime != null) + this.liveDate = new DateTime(sqlDateTime); + + switch (this.getSiegePhase()) { + + case CHALLENGE: + break; + case WAR: + + // cancel old job if exists + + if (this.activateBaneJob != null) + this.activateBaneJob.cancelJob(); + + abtj = new ActivateBaneJob(cityUUID); + JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); + this.activateBaneJob = abtj; + + break; + case STANDOFF: + + // cancel old job if exists + + if (this.activateBaneJob != null) + this.activateBaneJob.cancelJob(); + + abtj = new ActivateBaneJob(cityUUID); + JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); + this.activateBaneJob = abtj; + + break; + } + + if (this.liveDate == null) + setDefaultTime(); + } + + public static boolean summonBanestone(PlayerCharacter player, ClientConnection origin, int rank) { + + Guild baningGuild; + Zone cityZone; + City targetCity; + + ArrayList nationBanes; + + baningGuild = player.getGuild(); + + if (baningGuild.getNation().isEmptyGuild()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 55, ""); // You must be in a Nation + return false; + } + + if (baningGuild.getNation().isNPCGuild()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 72, ""); // Cannot be in an NPC nation + return false; + } + + if (GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) { + PlaceAssetMsg.sendPlaceAssetError(origin, 10, ""); // You must be a guild leader + return false; + } + + // Cannot place banestone underwater; + + if (HeightMap.isLocUnderwater(player.getLoc())) { + PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater + return false; + } + + // figure out which city zone we are standing on. + + targetCity = ZoneManager.getCityAtLocation(player.getLoc()); + + if (targetCity == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 59, ""); // No city to siege at this location! + return false; + } + + if (targetCity.isSafeHold()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 15, ""); // Cannot place assets in peace zone + return false; + } + + if (targetCity.getRank() > rank) { + PlaceAssetMsg.sendPlaceAssetError(origin, 60, ""); // Bane rank is too low + return false; + } + + cityZone = targetCity.getParent(); + + // Cannot place assets on a dead tree + + if ((cityZone.isPlayerCity()) && + (City.getCity(cityZone.getPlayerCityUUID()).getTOL().getRank() == -1)) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot bane a dead tree!"); + return false; + } + + if (baningGuild.getNation() == targetCity.getGuild().getNation()) { + PlaceAssetMsg.sendPlaceAssetError(origin, 20, ""); //Cannot bane yourself! + return false; + } + + if (targetCity.getTOL() == null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 65, ""); // Cannot find tree to target + return false; + } + + if (targetCity.getBane() != null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 23, ""); // Tree is already baned. + return false; + } + + if (getBaneByAttackerGuild(baningGuild) != null) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Your guild has already placed a bane!"); + return false; + } + + nationBanes = getBanesByNation(baningGuild.getNation()); + + // A nation can only have 3 concurrent banes + + if (nationBanes.size() == 3) { + PlaceAssetMsg.sendPlaceAssetError(origin, 64, ""); // Your nation is already at war and your limit has been reached + return false; + } + + if (targetCity.isLocationOnCityGrid(player.getLoc()) == true) { + PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot place banestone on city grid."); + return false; + } + + Blueprint blueprint = Blueprint.getBlueprint(24300); // Banestone + + //Let's drop a banestone! + + Vector3fImmutable localLocation = ZoneManager.worldToLocal(player.getLoc(), cityZone); + + if (localLocation == null) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + Logger.info("Failed to Convert World coordinates to local zone coordinates"); + return false; + } + + Building stone = DbManager.BuildingQueries.CREATE_BUILDING( + cityZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(), + localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank, + null, blueprint.getBlueprintUUID(), 1, 0.0f); + + if (stone == null) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + stone.addEffectBit((1 << 19)); + stone.setMaxHitPoints(stone.getBlueprint().getMaxHealth(stone.getRank())); + stone.setCurrentHitPoints(stone.getMaxHitPoints()); + BuildingManager.setUpgradeDateTime(stone, null, 0); + + //Make the bane + + Bane bane = makeBane(player, targetCity, stone); + + if (bane == null) { + //delete bane stone, failed to make bane object + DbManager.BuildingQueries.DELETE_FROM_DATABASE(stone); + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return false; + } + + WorldGrid.addObject(stone, player); + + //Add bane effect to TOL + + targetCity.getTOL().addEffectBit((1 << 16)); + targetCity.getTOL().updateEffects(); + + Vector3fImmutable movePlayerOutsideStone = player.getLoc(); + movePlayerOutsideStone = movePlayerOutsideStone.setX(movePlayerOutsideStone.x + 10); + movePlayerOutsideStone = movePlayerOutsideStone.setZ(movePlayerOutsideStone.z + 10); + player.teleport(movePlayerOutsideStone); + + // Notify players + + ChatSystemMsg chatMsg = new ChatSystemMsg(null, "[Bane Channel] " + targetCity.getCityName() + " has been baned by " + baningGuild.getName() + ". Standoff phase has begun!"); + chatMsg.setMessageType(4); + chatMsg.setChannel(Enum.ChatChannelType.SYSTEM.getChannelID()); + + DispatchMessage.dispatchMsgToAll(chatMsg); + + // Push this event to the DataWarehouse + + BaneRecord baneRecord = BaneRecord.borrow(bane, Enum.RecordEventType.PENDING); + DataWarehouse.pushToWarehouse(baneRecord); + + return true; + } + + public static Bane getBane(int cityUUID) { + + Bane outBane; + + // Check cache first + + outBane = banes.get(cityUUID); + + // Last resort attempt to load from database + + if (outBane == null) + outBane = DbManager.BaneQueries.LOAD_BANE(cityUUID); + else + return outBane; + + // As we loaded from the db, store it in the internal cache + + if (outBane != null) + banes.put(cityUUID, outBane); + + return outBane; + } + + public static Bane getBaneByAttackerGuild(Guild guild) { + + + if (guild == null || guild.isEmptyGuild()) + return null; + ArrayList baneList; + + baneList = new ArrayList<>(banes.values()); + + for (Bane bane : baneList) { + if (bane.getOwner().getGuild().equals(guild)) + return bane; + } + + return null; + } + + public static ArrayList getBanesByNation(Guild guild) { + + ArrayList baneList; + ArrayList returnList; + + baneList = new ArrayList<>(banes.values()); + returnList = new ArrayList<>(); + + for (Bane bane : baneList) { + if (bane.getOwner().getGuild().getNation().equals(guild)) + returnList.add(bane); + } + + return returnList; + } + + public static void addBane(Bane bane) { + + Bane.banes.put(bane.cityUUID, bane); + + } + + public static Bane makeBane(PlayerCharacter owner, City city, Building stone) { + + Bane newBane; + + if (DbManager.BaneQueries.CREATE_BANE(city, owner, stone) == false) { + Logger.error("Error writing to database"); + return null; + } + + newBane = DbManager.BaneQueries.LOAD_BANE(city.getObjectUUID()); + + return newBane; + } + + //Call this to prematurely end a bane + + public SiegePhase getSiegePhase() { + + SiegePhase phase; + + if (this.liveDate == null) { + phase = SiegePhase.CHALLENGE; //challenge + return phase; + } + + if (DateTime.now().isAfter(this.liveDate)) { + phase = SiegePhase.WAR; //war + return phase; + } + + // If code reaches this point we are in standoff mode. + phase = SiegePhase.STANDOFF; //standoff + + return phase; + } + + // Cache access + + private void setDefaultTime() { + + DateTime timeToSetDefault = new DateTime(this.placementDate); + timeToSetDefault = timeToSetDefault.plusDays(1); + + DateTime currentTime = DateTime.now(); + DateTime defaultTime = new DateTime(this.placementDate); + defaultTime = defaultTime.plusDays(2); + defaultTime = defaultTime.hourOfDay().setCopy(22); + defaultTime = defaultTime.minuteOfHour().setCopy(0); + defaultTime = defaultTime.secondOfMinute().setCopy(0); + + if (currentTime.isAfter(timeToSetDefault)) + this.setLiveDate(defaultTime); + else { + + if (this.defaultTimeJob != null) + this.defaultTimeJob.cancelJob(); + + BaneDefaultTimeJob bdtj = new BaneDefaultTimeJob(this); + JobScheduler.getInstance().scheduleJob(bdtj, timeToSetDefault.getMillis()); + this.defaultTimeJob = bdtj; + } + } + + //Returns a guild's bane + + /* + * Getters + */ + public City getCity() { + return City.getCity(this.cityUUID); + } + + public PlayerCharacter getOwner() { + return (PlayerCharacter) DbManager.getObject(Enum.GameObjectType.PlayerCharacter, ownerUUID); + } + + public boolean remove() { + + Building baneStone; + + baneStone = this.getStone(); + + if (baneStone == null) { + Logger.debug("Removing bane without a stone."); + return false; + } + + // Reassert protection contracts + + this.getCity().protectionEnforced = true; + + // Remove visual effects on Bane and TOL. + + this.getStone().removeAllVisualEffects(); + this.getStone().updateEffects(); + + this.getCity().getTOL().removeAllVisualEffects(); + this.getCity().getTOL().updateEffects(); + + // Remove bane from the database + + if (DbManager.BaneQueries.REMOVE_BANE(this) == false) { + Logger.error("Database call failed for city UUID: " + this.getCity().getObjectUUID()); + return false; + } + + // Remove bane from ingame cache + + Bane.banes.remove(cityUUID); + + // Delete stone from database + + if (DbManager.BuildingQueries.DELETE_FROM_DATABASE(baneStone) == false) { + Logger.error("Database error when deleting stone object"); + return false; + } + + // Remove object from simulation + + baneStone.removeFromCache(); + WorldGrid.RemoveWorldObject(baneStone); + WorldGrid.removeObject(baneStone); + return true; + } + + public final DateTime getPlacementDate() { + return placementDate; + } + + public final boolean isAccepted() { + + return (this.getSiegePhase() != SiegePhase.CHALLENGE); + } + + public final DateTime getLiveDate() { + return liveDate; + } + + public void setLiveDate(DateTime baneTime) { + + if (DbManager.BaneQueries.SET_BANE_TIME(baneTime, this.getCity().getObjectUUID())) { + this.liveDate = new DateTime(baneTime); + + // Push event to warehouse + + BaneRecord.updateLiveDate(this, baneTime); + + ChatManager.chatGuildInfo(this.getOwner().getGuild(), "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); + Guild attackerNation = this.getOwner().getGuild().getNation(); + + if (attackerNation != null) + + for (Guild subGuild : attackerNation.getSubGuildList()) { + + //Don't send the message twice. + if (subGuild.equals(attackerNation)) + continue; + + ChatManager.chatGuildInfo(subGuild, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); + } + + ChatManager.chatGuildInfo(this.getCity().getGuild(), "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); + + Guild defenderNation = this.getCity().getGuild().getNation(); + + if (defenderNation != null) { + + if (defenderNation != this.getCity().getGuild()) + ChatManager.chatGuildInfo(defenderNation, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); + + for (Guild subGuild : defenderNation.getSubGuildList()) { + //Don't send the message twice. + if (subGuild == this.getCity().getGuild()) + continue; + ChatManager.chatGuildInfo(subGuild, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); + } + } + + if (activateBaneJob != null) + this.activateBaneJob.cancelJob(); + + ActivateBaneJob abtj = new ActivateBaneJob(cityUUID); + + JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); + this.activateBaneJob = abtj; + } else { + Logger.debug("error with city " + this.getCity().getName()); + ChatManager.chatGuildInfo(this.getOwner().getGuild(), "A Serious error has occurred. Please post details for to ensure transaction integrity"); + ChatManager.chatGuildInfo(this.getCity().getGuild(), "A Serious error has occurred. Please post details for to ensure transaction integrity"); + } + + } + + public final void endBane(SiegeResult siegeResult) { + + boolean baneRemoved; + + // No matter what the outcome of a bane, we re-asset + // protection contracts at this time. They don't quite + // matter if the city falls, as they are invalidated. + + this.getCity().protectionEnforced = true; + + switch (siegeResult) { + case DEFEND: + + // Push event to warehouse + + BaneRecord.updateResolution(this, Enum.RecordEventType.DEFEND); + + baneRemoved = remove(); + + if (baneRemoved) { + + // Update seieges withstood + + this.getCity().setSiegesWithstood(this.getCity().getSiegesWithstood() + 1); + + // Notify players + + ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getCity().getGuild().getName() + " has rallied against " + this.getOwner().getGuild().getName() + ". The siege on " + this.getCity().getCityName() + " has been broken!"); + msg.setMessageType(4); + msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID()); + + DispatchMessage.dispatchMsgToAll(msg); + + } + + break; + case CAPTURE: + + // Push event to warehouse + + BaneRecord.updateResolution(this, Enum.RecordEventType.CAPTURE); + + baneRemoved = this.remove(); + + if (baneRemoved) { + + ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getOwner().getGuild().getName() + " have defeated " + this.getCity().getGuild().getName() + " and captured " + this.getCity().getCityName() + '!'); + msg.setMessageType(4); + msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID()); + + DispatchMessage.dispatchMsgToAll(msg); + } + break; + case DESTROY: + + // Push event to warehouse + + BaneRecord.updateResolution(this, Enum.RecordEventType.DESTROY); + + baneRemoved = this.remove(); + + if (baneRemoved) { + + ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getOwner().getGuild().getName() + " have defeated " + this.getCity().getGuild().getName() + " and razed " + this.getCity().getCityName() + '!'); + msg.setMessageType(4); + msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID()); + + DispatchMessage.dispatchMsgToAll(msg); + } + break; + } + + Zone cityZone = this.getCity().getParent(); + + if (cityZone == null) + return; + + //UNPROTECT ALL SIEGE EQUIPMENT AFTER A BANE + for (Building toUnprotect : cityZone.zoneBuildingSet) { + if (toUnprotect.getBlueprint() != null && toUnprotect.getBlueprint().isSiegeEquip() && toUnprotect.assetIsProtected() == true) + toUnprotect.setProtectionState(ProtectionState.NONE); + } + + } + + public boolean isErrant() { + + boolean isErrant = true; + + if (this.getOwner() == null) + return isErrant; + + + if (this.getOwner().getGuild().isEmptyGuild() == true) + return isErrant; + + if (this.getOwner().getGuild().getNation().isEmptyGuild() == true) + return isErrant; + + // Bane passes validation + + isErrant = false; + + return isErrant; + } + + /** + * @return the stone + */ + public Building getStone() { + return BuildingManager.getBuilding(this.stoneUUID); + } + + /** + * @return the cityUUID + */ + public int getCityUUID() { + return cityUUID; + } + +} diff --git a/src/engine/objects/BaseClass.java b/src/engine/objects/BaseClass.java new file mode 100644 index 00000000..4e4eae31 --- /dev/null +++ b/src/engine/objects/BaseClass.java @@ -0,0 +1,218 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + + +public class BaseClass extends AbstractGameObject { + + private final String name; + private final String description; + + private final byte strMod; + private final byte dexMod; + private final byte conMod; + private final byte intMod; + private final byte spiMod; + + private final float healthMod; + private final float manaMod; + private final float staminaMod; + private final ArrayList skillsGranted; + private final ArrayList powersGranted; + private int token = 0; + private ArrayList effectsList = new ArrayList<>(); + + + /** + * No Table ID Constructor + */ + public BaseClass(String name, String description, byte strMod, byte dexMod, byte conMod, byte intMod, byte spiMod, + ArrayList allowedRunes, ArrayList skillsGranted, ArrayList powersGranted) { + super(); + this.name = name; + this.description = description; + this.strMod = strMod; + this.dexMod = dexMod; + this.conMod = conMod; + this.intMod = intMod; + this.spiMod = spiMod; + this.healthMod = 1; + this.manaMod = 1; + this.staminaMod = 1; + + this.skillsGranted = skillsGranted; + this.powersGranted = powersGranted; + + } + + /** + * Normal Constructor + */ + public BaseClass(String name, String description, byte strMod, byte dexMod, byte conMod, byte intMod, byte spiMod, + ArrayList allowedRunes, ArrayList skillsGranted, ArrayList powersGranted, int newUUID) { + super(newUUID); + this.name = name; + this.description = description; + this.strMod = strMod; + this.dexMod = dexMod; + this.conMod = conMod; + this.intMod = intMod; + this.spiMod = spiMod; + this.healthMod = 1; + this.manaMod = 1; + this.staminaMod = 1; + this.skillsGranted = skillsGranted; + this.powersGranted = powersGranted; + + } + + + /** + * ResultSet Constructor + */ + public BaseClass(ResultSet rs) throws SQLException { + super(rs); + + this.name = rs.getString("name"); + this.description = rs.getString("description"); + this.strMod = rs.getByte("strMod"); + this.dexMod = rs.getByte("dexMod"); + this.conMod = rs.getByte("conMod"); + this.intMod = rs.getByte("intMod"); + this.spiMod = rs.getByte("spiMod"); + this.token = rs.getInt("token"); + this.healthMod = rs.getInt("healthMod"); + this.manaMod = rs.getInt("manaMod"); + this.staminaMod = rs.getInt("staminaMod"); + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID()); + this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID()); + this.effectsList = (DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID())); + + } + + public static void LoadAllBaseClasses() { + DbManager.BaseClassQueries.GET_ALL_BASE_CLASSES(); + } + + public static void serializeForClientMsg(BaseClass baseClass, ByteBufferWriter writer) { + serializeForClientMsg(baseClass, writer, 3); + } + + public static void serializeForClientMsg(BaseClass baseClass, ByteBufferWriter writer, int type) { + writer.putInt(type); // For BaseClass + writer.putInt(0); // Pad + writer.putInt(baseClass.getObjectUUID()); + writer.putInt(baseClass.getObjectType().ordinal()); // Is this correct? + writer.putInt(baseClass.getObjectUUID()); + } + + public static BaseClass getBaseClass(final int UUID) { + return DbManager.BaseClassQueries.GET_BASE_CLASS(UUID); + } + + /* + * Getters + */ + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public byte getStrMod() { + return strMod; + } + + public byte getDexMod() { + return dexMod; + } + + public byte getConMod() { + return conMod; + } + + public byte getIntMod() { + return intMod; + } + + public byte getSpiMod() { + return spiMod; + } + + public int getToken() { + return this.token; + } + + public float getHealthMod() { + return this.healthMod; + } + + public float getManaMod() { + return this.manaMod; + } + + public float getStaminaMod() { + return this.staminaMod; + } + + public ArrayList getRuneList() { + return RuneBase.AllowedBaseClassRunesMap.get(this.getObjectUUID()); + } + + public ArrayList getSkillsGranted() { + return this.skillsGranted; + } + + /* + * Serializing + */ + + public ArrayList getPowersGranted() { + return this.powersGranted; + } + + public ArrayList getEffectsGranted() { + return RuneBaseEffect.RuneIDBaseEffectMap.get(this.getObjectUUID()); + } + + /* + * Utils + */ + public boolean isAllowedRune(RuneBase rb) { + + if (this.getRuneList().contains(rb.getObjectUUID())) + return true; + + if (RuneBase.AllowedBaseClassRunesMap.containsKey(111111)) { + if (RuneBase.AllowedBaseClassRunesMap.get(111111).contains(rb.getObjectUUID())) + return true; + } + return false; + } + + @Override + public void updateDatabase() { + ; //Never update.. + } + + public ArrayList getEffectsList() { + return effectsList; + } + +} diff --git a/src/engine/objects/Blueprint.java b/src/engine/objects/Blueprint.java new file mode 100644 index 00000000..15777c27 --- /dev/null +++ b/src/engine/objects/Blueprint.java @@ -0,0 +1,628 @@ +package engine.objects; + +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +import engine.Enum.BuildingGroup; +import engine.gameManager.DbManager; +import engine.math.Vector2f; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + +/* @Summary - Blueprint class is used for determining + characteristics of instanced player owned + structures such as available slots, upgrade + cost/time and the target window symbol icon. + */ +public class Blueprint { + + public final static Vector2f IrikieForgeExtents = new Vector2f(32, 32); + public final static Vector2f IrikieBarracksExtents = new Vector2f(32, 32); + public static HashMap _meshLookup = new HashMap<>(); + private static HashMap _blueprints = new HashMap<>(); + private static HashMap _doorNumbers = new HashMap<>(); + private final int blueprintUUID; + private final String name; + private final BuildingGroup buildingGroup; + private final int icon; + private final int maxRank; + private final int maxSlots; + private final int rank1UUID; + private final int rank3UUID; + private final int rank7UUID; + private final int destroyedUUID; + + private Blueprint() { + this.blueprintUUID = 0; + this.name = ""; + this.icon = 0; + this.buildingGroup = BuildingGroup.BANESTONE; + this.maxRank = 0; + this.maxSlots = 0; + this.rank1UUID = 0; + this.rank3UUID = 0; + this.rank7UUID = 0; + this.destroyedUUID = 0; + } + + public Blueprint(ResultSet rs) throws SQLException { + + this.blueprintUUID = rs.getInt("Rank0UUID"); + this.name = rs.getString("MeshName"); + this.icon = rs.getInt("Icon"); + this.buildingGroup = BuildingGroup.valueOf(rs.getString("BuildingGroup")); + this.maxRank = rs.getInt("MaxRank"); + this.maxSlots = rs.getInt("MaxSlots"); + this.rank1UUID = rs.getInt("Rank1UUID"); + this.rank3UUID = rs.getInt("Rank3UUID"); + this.rank7UUID = rs.getInt("Rank7UUID"); + this.destroyedUUID = rs.getInt("DestroyedUUID"); + + } + + // Accessors + + public static Blueprint getBlueprint(int blueprintUUID) { + + return _blueprints.get(blueprintUUID); + + } + + public static BuildingGroup getBuildingGroup(int blueprintUUID) { + + Blueprint blueprint; + + blueprint = _blueprints.get(blueprintUUID); + + return blueprint.buildingGroup; + } + + public static int getMaxShrines(int treeRank) { + + // Returns the number of allowed spires/shrines + // for a given rank. + + int maxShrines; + + switch (treeRank) { + case 0: + case 1: + case 2: + maxShrines = 0; + break; + case 3: + case 4: + maxShrines = 1; + break; + case 5: + case 6: + maxShrines = 2; + break; + case 7: + case 8: + maxShrines = 3; + break; + default: + maxShrines = 0; + + } + + return maxShrines; + } + + public static void loadAllBlueprints() { + + _blueprints = DbManager.BlueprintQueries.LOAD_ALL_BLUEPRINTS(); + + } + + // Method returns a blueprint based on a blueprintUUID + + public static void loadAllDoorNumbers() { + + _doorNumbers = DbManager.BlueprintQueries.LOAD_ALL_DOOR_NUMBERS(); + + } + + public static int getDoorNumberbyMesh(int doorMeshUUID) { + + if (_doorNumbers.containsKey(doorMeshUUID)) + return _doorNumbers.get(doorMeshUUID); + + return 0; + } + + public static boolean isMeshWallPiece(int meshUUID) { + + Blueprint buildingBlueprint = Blueprint.getBlueprint(meshUUID); + + if (buildingBlueprint == null) + return false; + + switch (buildingBlueprint.buildingGroup) { + case WALLSTRAIGHT: + case ARTYTOWER: + case WALLCORNER: + case SMALLGATE: + case WALLSTAIRS: + case WALLSTRAIGHTTOWER: + return true; + default: + break; + } + return false; + + } + + // Method calculates available vendor slots + // based upon the building's current rank + + public static int getNpcMaintCost(int rank) { + int maintCost = Integer.MAX_VALUE; + + maintCost = (9730 * rank) + 1890; + + return maintCost; + } + + public int getMaxRank() { + return maxRank; + } + + public int getMaxSlots() { + if (this.buildingGroup != null && this.buildingGroup.equals(BuildingGroup.BARRACK)) + return 1; + return maxSlots; + } + + // Method returns a mesh UUID for this blueprint + // based upon a given rank. + + public BuildingGroup getBuildingGroup() { + return this.buildingGroup; + } + + // Method returns a cost to upgrade a building to a given rank + // based upon this blueprint's maintenance group + + public int getMaxHealth(int currentRank) { + + int maxHealth; + + // Return 0 health for a destroyed building + // or 1 for a destroyed mine (cleint looting restriction) + + if (currentRank == -1) { + + return this.buildingGroup == BuildingGroup.MINE ? 1 : 0; + } + + // Return 15k for a constructing mesh + + if (currentRank == 0) + return 15000; + + switch (this.buildingGroup) { + + case TOL: + maxHealth = (70000 * currentRank) + 10000; + break; + case BARRACK: + maxHealth = (35000 * currentRank) + 5000; + break; + case BANESTONE: + maxHealth = (170000 * currentRank) - 120000; + break; + case CHURCH: + maxHealth = (28000 * currentRank) + 4000; + break; + case MAGICSHOP: + case FORGE: + case INN: + case TAILOR: + maxHealth = (17500 * currentRank) + 2500; + break; + case VILLA: + case ESTATE: + case FORTRESS: + maxHealth = 300000; + break; + case CITADEL: + maxHealth = 500000; + break; + case SPIRE: + maxHealth = (37000 * currentRank) - 9000; + break; + case GENERICNOUPGRADE: + case SHACK: + case SIEGETENT: + maxHealth = 40000; + break; + case BULWARK: + if (currentRank == 1) + maxHealth = 110000; + else + maxHealth = 40000; + break; + case WALLSTRAIGHT: + case WALLSTRAIGHTTOWER: + case WALLSTAIRS: + maxHealth = 1000000; + break; + case WALLCORNER: + case ARTYTOWER: + maxHealth = 900000; + break; + case SMALLGATE: + maxHealth = 1100000; + break; + case AMAZONHALL: + case CATHEDRAL: + case GREATHALL: + case KEEP: + case THIEFHALL: + case TEMPLEHALL: + case WIZARDHALL: + case ELVENHALL: + case ELVENSANCTUM: + case IREKEIHALL: + case FORESTHALL: + maxHealth = (28000 * currentRank) + 4000; + break; + case MINE: + maxHealth = 125000; + break; + case RUNEGATE: + maxHealth = 100000; + break; + case SHRINE: + maxHealth = 100000; + break; + case WAREHOUSE: + maxHealth = 40000; + break; + + default: + maxHealth = 40000; + break; + + } + return maxHealth; + } + + // Returns number of vendor slots available + // for the building's current rank. + + public int getSlotsForRank(int currentRank) { + + int availableSlots; + + // Early exit for buildings not yet constructed + + if (currentRank == 0) + return 0; + + // Early exit for buildings with single or no slots + + if (this.maxSlots <= 1) + return maxSlots; + + if (this.maxRank == 1 && currentRank == 1) + return getMaxSlots(); + + switch (currentRank) { + + case 1: + case 2: + availableSlots = 1; + break; + case 3: + case 4: + case 5: + case 6: + availableSlots = 2; + break; + case 7: + availableSlots = 3; + break; + case 8: + availableSlots = 1; + break; + default: + availableSlots = 0; + break; + } + + return availableSlots; + } + + // Returns the half extents of this blueprint's + // bounding box, based upon it's buildinggroup + + public int getIcon() { + return this.icon; + } + + public String getName() { + return this.name; + } + + public int getMeshForRank(int targetRank) { + + int targetMesh = this.blueprintUUID; + + // The Blueprint UUID is the 'constructing' mesh so + // we return that value if the rank passed is 0. + + if ((maxRank == 1) && (this.rank1UUID == 0)) { + return blueprintUUID; + } + + // Set the return value to the proper mesh UID for rank + + switch (targetRank) { + + case -1: + targetMesh = this.destroyedUUID; // -1 Rank is a destroyed mesh + break; + case 0: + targetMesh = this.blueprintUUID; // Rank 0 is the 'constructing' mesh + break; + case 1: + case 2: + targetMesh = this.rank1UUID; + break; + case 3: + case 4: + case 5: + case 6: + targetMesh = this.rank3UUID; + break; + case 7: + case 8: + targetMesh = this.rank7UUID; + break; + default: + break; + } + + return targetMesh; + } + + public int getRankCost(int targetRank) { + + // Set a MAXINT rankcost in case something goes wrong + + int rankCost = Integer.MAX_VALUE; + + // Sanity chack for retrieving a rankcost outside proper range + + if ((targetRank > maxRank) || (targetRank < 0)) { + Logger.error("Attempt to retrieve rankcost for rank of" + targetRank); + return rankCost; + } + + // Select linear equation for rank cost based upon the + // buildings current Maintenance BuildingGroup. + + switch (this.buildingGroup) { + + case GENERICNOUPGRADE: + case WALLSTRAIGHT: + case WALLSTAIRS: + case WALLCORNER: + case SMALLGATE: + case ARTYTOWER: + case SIEGETENT: + case BULWARK: + case BANESTONE: + case SHACK: + break; // This set cannot be upgraded. Returns max integer. + + case TOL: + rankCost = (880000 * targetRank) - 440000; + break; + case BARRACK: + case VILLA: + case ESTATE: + case FORTRESS: + case CITADEL: + rankCost = (451000 * targetRank) - 308000; + break; + case CHURCH: + rankCost = (682000 * targetRank) - 110000; + break; + case FORGE: + case INN: + case TAILOR: + case MAGICSHOP: + rankCost = (440000 * targetRank) - 550000; + break; + case SPIRE: + rankCost = (176000 * targetRank) - 88000; + break; + case AMAZONHALL: + case CATHEDRAL: + case GREATHALL: + case KEEP: + case THIEFHALL: + case TEMPLEHALL: + case WIZARDHALL: + case ELVENHALL: + case ELVENSANCTUM: + case IREKEIHALL: + case FORESTHALL: + rankCost = (682000 * targetRank) - 110000; + break; + default: + Logger.error("Attempt to retrieve rankcost without MaintGroup for " + this.buildingGroup.name()); + break; + } + + return rankCost; + } + + public int getRankTime(int targetRank) { + + // Set a very long rankTime in case something goes wrong + + int rankTime = (Integer.MAX_VALUE / 2); + + // Set all initial construction to a default of 4 hours. + + if (targetRank == 1) + return 4; + + // Sanity chack for retrieving a ranktime outside proper range + + if ((targetRank > maxRank) || (targetRank < 1)) { + Logger.error("Attempt to retrieve ranktime for rank of" + targetRank); + return rankTime; + } + + // Select equation for rank time based upon the + // buildings current Maintenance BuildingGroup. These values + // are expressed in hours + + switch (this.buildingGroup) { + + case GENERICNOUPGRADE: + break; // Cannot be upgraded + case VILLA: + case ESTATE: + case FORTRESS: + case CITADEL: + rankTime = (7 * targetRank) - 7; + break; + case TOL: + rankTime = (7 * targetRank) - 7; + break; + case BARRACK: + rankTime = (7 * targetRank) - 7; + break; + case CHURCH: + rankTime = (7 * targetRank) - 7; + break; + case FORGE: + case INN: + case TAILOR: + case MAGICSHOP: + rankTime = (7 * targetRank) - 7; + break; + case SPIRE: + rankTime = (4 * targetRank) + 4; + break; + case AMAZONHALL: + case CATHEDRAL: + case GREATHALL: + case KEEP: + case THIEFHALL: + case TEMPLEHALL: + case WIZARDHALL: + case ELVENHALL: + case ELVENSANCTUM: + case IREKEIHALL: + case FORESTHALL: + rankTime = (7 * targetRank) - 7; + break; + default: + Logger.error("Attempt to retrieve ranktime without MaintGroup"); + break; + } + + return rankTime; + } + + public Vector2f getExtents() { + + if (blueprintUUID == 1302600) + return Blueprint.IrikieForgeExtents; + else if (blueprintUUID == 1300600) + return Blueprint.IrikieBarracksExtents; + + return this.buildingGroup.getExtents(); + + } + + public boolean isWallPiece() { + + switch (this.buildingGroup) { + case WALLSTRAIGHT: + case WALLSTAIRS: + case ARTYTOWER: + case WALLCORNER: + case SMALLGATE: + return true; + default: + break; + } + return false; + } + + public boolean isSiegeEquip() { + + switch (this.buildingGroup) { + case BULWARK: + case SIEGETENT: + return true; + default: + break; + } + return false; + + } + + public int getBlueprintUUID() { + return blueprintUUID; + } + + + @Override + public boolean equals(Object object) { + + if ((object instanceof Blueprint) == false) + return false; + + Blueprint blueprint = (Blueprint) object; + + return this.blueprintUUID == blueprint.blueprintUUID; + } + + @Override + public int hashCode() { + + return this.blueprintUUID; + } + + public int getMaintCost(int rank) { + + int maintCost = Integer.MAX_VALUE; + + switch (this.buildingGroup) { + case TOL: + case BARRACK: + maintCost = (61500 * rank) + 19500; + break; + case SPIRE: + maintCost = (4800 * rank) + 1200; + break; + default: + if (maxRank == 1) + maintCost = 22500; + else + maintCost = (15900 * rank) + 3300; + break; + } + + return maintCost; + } +} diff --git a/src/engine/objects/Boon.java b/src/engine/objects/Boon.java new file mode 100644 index 00000000..2cdcfd94 --- /dev/null +++ b/src/engine/objects/Boon.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.ShrineType; +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + + +public class Boon { + + public static HashMap> GetBoonsForItemBase = new HashMap<>(); + private ShrineType shrineType; + private int amount; + private int itemBaseID; + + + /** + * ResultSet Constructor + */ + public Boon(ResultSet rs) throws SQLException { + + this.shrineType = ShrineType.valueOf(rs.getString("shrineType")); + this.itemBaseID = rs.getInt("itemBaseID"); + this.amount = rs.getInt("amount"); + } + + public static void HandleBoonListsForItemBase(int itemBaseID) { + ArrayList boons = null; + boons = DbManager.BoonQueries.GET_BOON_AMOUNTS_FOR_ITEMBASE(itemBaseID); + if (boons != null) + GetBoonsForItemBase.put(itemBaseID, boons); + } + + public int getAmount() { + return this.amount; + } + + public int getItemBaseID() { + return itemBaseID; + } + + public ShrineType getShrineType() { + return shrineType; + } + + +} diff --git a/src/engine/objects/Building.java b/src/engine/objects/Building.java new file mode 100644 index 00000000..d625234c --- /dev/null +++ b/src/engine/objects/Building.java @@ -0,0 +1,1715 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.*; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.RealmMap; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.CityRecord; +import engine.db.archive.DataWarehouse; +import engine.db.archive.MineRecord; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.DoorCloseJob; +import engine.jobs.SiegeSpireWithdrawlJob; +import engine.math.Bounds; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.net.ByteBufferWriter; +import engine.net.DispatchMessage; +import engine.net.client.msg.ApplyBuildingEffectMsg; +import engine.net.client.msg.UpdateObjectMsg; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +public class Building extends AbstractWorldObject { + + // Used for thread safety + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ConcurrentHashMap hirelings = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final HashMap doorJobs = new HashMap<>(); + public int meshUUID; + public Zone parentZone; + public boolean reverseKOS; + public int reserve = 0; + public float statLat; + public float statLon; + public float statAlt; + public LocalDateTime upgradeDateTime = null; + public LocalDateTime taxDateTime = null; + public ArrayList patrolPoints = new ArrayList<>(); + public ArrayList sentryPoints = new ArrayList<>(); + public TaxType taxType = TaxType.NONE; + public int taxAmount; + public boolean enforceKOS = false; + + // Variables NOT to be stored in db + public int parentBuildingID; + public boolean isFurniture = false; + public int floor; + public int level; + public AtomicBoolean isDeranking = new AtomicBoolean(false); + public LocalDateTime maintDateTime; + protected Resists resists; + /* The Blueprint class has methods able to derive + * all defining characteristics of this building, + */ + private int blueprintUUID = 0; + private float w = 1.0f; + private Vector3f meshScale = new Vector3f(1.0f, 1.0f, 1.0f); + private int doorState = 0; + private int ownerUUID = 0; //NPC or Character--check ownerIsNPC flag + private int _strongboxValue = 0; + private int maxGold; + private int effectFlags = 0; + private String name = ""; + private int rank; + private boolean ownerIsNPC = true; + private boolean spireIsActive = false; + private ConcurrentHashMap timers = null; + private ConcurrentHashMap timestamps = null; + private ConcurrentHashMap friends = new ConcurrentHashMap<>(); + private ConcurrentHashMap condemned = new ConcurrentHashMap<>(); + private ProtectionState protectionState = ProtectionState.NONE; + private ArrayList children = null; + + /** + * ResultSet Constructor + */ + + public Building(ResultSet rs) throws SQLException { + super(rs); + + float scale; + Blueprint blueprint = null; + + try { + this.meshUUID = rs.getInt("meshUUID"); + this.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + this.blueprintUUID = rs.getInt("blueprintUUID"); + this.gridObjectType = GridObjectType.STATIC; + this.parentZone = DbManager.ZoneQueries.GET_BY_UID(rs.getLong("parent")); + this.name = rs.getString("name"); + this.ownerUUID = rs.getInt("ownerUUID"); + + // Orphaned Object Sanity Check + //This was causing ABANDONED Tols. + // if (objectType == DbObjectType.INVALID) + // this.ownerUUID = 0; + + this.doorState = rs.getInt("doorState"); + this.setHealth(rs.getInt("currentHP")); + this.w = rs.getFloat("w"); + this.setRot(new Vector3f(0f, rs.getFloat("rotY"), 0f)); + this.reverseKOS = rs.getByte("reverseKOS") == 1 ? true : false; + + scale = rs.getFloat("scale"); + this.meshScale = new Vector3f(scale, scale, scale); + + this.rank = rs.getInt("rank"); + this.parentBuildingID = rs.getInt("parentBuildingID"); + + //create a new list if the building is a parent and not a child. + + if (this.parentBuildingID == 0) + this.children = new ArrayList<>(); + + this.floor = rs.getInt("floor"); + this.level = rs.getInt("level"); + this.isFurniture = (rs.getBoolean("isFurniture")); + + // Lookup building blueprint + + if (this.blueprintUUID == 0) + blueprint = Blueprint._meshLookup.get(meshUUID); + else + blueprint = this.getBlueprint(); + + // Log error if something went horrible wrong + + if ((this.blueprintUUID != 0) && (blueprint == null)) + Logger.error("Invalid blueprint for object: " + this.getObjectUUID()); + + // Note: We handle R8 tree edge case for mesh and health + // after city is loaded to avoid recursive result set call + // in City resulting in a stack ovreflow. + + if (blueprint != null) { + + // Only switch mesh for player dropped structures + + if (this.blueprintUUID != 0) + this.meshUUID = blueprint.getMeshForRank(rank); + + this.healthMax = blueprint.getMaxHealth(this.rank); + + // If this object has no blueprint but is a blueprint + // mesh then set it's current health to max health + + if (this.blueprintUUID == 0) + this.setHealth(healthMax); + + if (blueprint.getBuildingGroup().equals(BuildingGroup.BARRACK)) + this.patrolPoints = DbManager.BuildingQueries.LOAD_PATROL_POINTS(this); + + } else { + this.healthMax = 100000; // Structures with no blueprint mesh + this.setHealth(healthMax); + } + + // Null out blueprint if not needed (npc building) + + if (blueprintUUID == 0) + blueprint = null; + + resists = new Resists("Building"); + this.statLat = rs.getFloat("locationX"); + this.statAlt = rs.getFloat("locationY"); + this.statLon = rs.getFloat("locationZ"); + + if (this.parentZone != null) { + if (this.parentBuildingID != 0) { + Building parentBuilding = BuildingManager.getBuilding(this.parentBuildingID); + if (parentBuilding != null) { + this.setLoc(new Vector3fImmutable(this.statLat + this.parentZone.absX + parentBuilding.statLat, this.statAlt + this.parentZone.absY + parentBuilding.statAlt, this.statLon + this.parentZone.absZ + parentBuilding.statLon)); + } else { + this.setLoc(new Vector3fImmutable(this.statLat + this.parentZone.absX, this.statAlt + this.parentZone.absY, this.statLon + this.parentZone.absZ)); + + } + } else { + + // Altitude of this building is derived from the heightmap engine. + + Vector3fImmutable tempLoc = new Vector3fImmutable(this.statLat + this.parentZone.absX, 0, this.statLon + this.parentZone.absZ); + tempLoc = new Vector3fImmutable(tempLoc.x, HeightMap.getWorldHeight(tempLoc), tempLoc.z); + this.setLoc(tempLoc); + } + } + + this._strongboxValue = rs.getInt("currentGold"); + this.maxGold = 15000000; // *** Refactor to blueprint method + this.reserve = rs.getInt("reserve"); + + // Does building have a protection contract? + this.taxType = TaxType.valueOf(rs.getString("taxType")); + this.taxAmount = rs.getInt("taxAmount"); + this.protectionState = ProtectionState.valueOf(rs.getString("protectionState")); + + java.sql.Timestamp maintTimeStamp = rs.getTimestamp("maintDate"); + + if (maintTimeStamp != null) + this.maintDateTime = LocalDateTime.ofInstant(maintTimeStamp.toInstant(), ZoneId.systemDefault()); + + java.sql.Timestamp taxTimeStamp = rs.getTimestamp("taxDate"); + + if (taxTimeStamp != null) + this.taxDateTime = LocalDateTime.ofInstant(taxTimeStamp.toInstant(), ZoneId.systemDefault()); + + java.sql.Timestamp upgradeTimeStamp = rs.getTimestamp("upgradeDate"); + + if (upgradeTimeStamp != null) + this.upgradeDateTime = LocalDateTime.ofInstant(upgradeTimeStamp.toInstant(), ZoneId.systemDefault()); + + } catch (Exception e) { + + Logger.error("Failed for object " + this.blueprintUUID + ' ' + this.getObjectUUID() + e.toString()); + } + } + + /* + * Getters + */ + + public static void _serializeForClientMsg(Building building, ByteBufferWriter writer) { + writer.putInt(building.getObjectType().ordinal()); + writer.putInt(building.getObjectUUID()); + writer.putInt(0); // pad + + writer.putInt(building.meshUUID); + + writer.putInt(0); // pad + + if (building.parentBuildingID != 0) { + + writer.putFloat(building.statLat); + writer.putFloat(building.statAlt); + writer.putFloat(building.statLon); + + } else { + writer.putFloat(building.getLoc().getX()); + writer.putFloat(building.getLoc().getY()); // Y location + writer.putFloat(building.getLoc().getZ()); + } + + writer.putFloat(building.w); + writer.putFloat(0f); + writer.putFloat(building.getRot().y); + + writer.putFloat(0f); + writer.putFloat(building.meshScale.getX()); + writer.putFloat(building.meshScale.getY()); + writer.putFloat(building.meshScale.getZ()); + + if (building.parentBuildingID != 0) { + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(building.parentBuildingID); + writer.putInt(building.floor); + writer.putInt(building.level); + + } else { + writer.putInt(0); // Pad //Parent + writer.putInt(0); // Pad + writer.putInt(-1); // Static + writer.putInt(-1); // Static + } + + writer.put((byte) 0); // 0 + writer.putFloat(3); // 3 + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(building.getObjectUUID()); + + if (building.ownerIsNPC) + writer.putInt(GameObjectType.NPC.ordinal()); + else + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + + writer.putInt(building.ownerUUID); + + writer.put((byte) 1); // End Datablock + writer.putFloat(building.health.get()); + writer.putFloat(building.healthMax); + + if (building.blueprintUUID == 0) + writer.putInt(0); + else + writer.putInt(building.getBlueprint().getIcon()); + + writer.putInt(building.effectFlags); + + writer.put((byte) 1); // End Datablock + Guild g = building.getGuild(); + Guild nation = null; + + if (g == null) { + + for (int i = 0; i < 3; i++) { + writer.putInt(16); + } + for (int i = 0; i < 2; i++) { + writer.putInt(0); + } + } else { + GuildTag._serializeForDisplay(g.getGuildTag(), writer); + nation = g.getNation(); + } + writer.put((byte) 1); // End Datablock? + if (nation == null) { + for (int i = 0; i < 3; i++) { + writer.putInt(16); + } + for (int i = 0; i < 2; i++) { + writer.putInt(0); + } + } else + GuildTag._serializeForDisplay(nation.getGuildTag(), writer); + writer.putString(building.name); + writer.put((byte) 0); // End datablock + } + + public final boolean isRanking() { + + return this.upgradeDateTime != null; + } + + public final int getRank() { + return rank; + } + + public final void setRank(int newRank) { + + int newMeshUUID; + boolean success; + + + // If this building has no blueprint then set rank and exit immediatly. + + if (this.blueprintUUID == 0 || this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.MINE)) { + this.rank = newRank; + DbManager.BuildingQueries.CHANGE_RANK(this.getObjectUUID(), newRank); + return; + } + + // Delete any upgrade jobs before doing anything else. It won't quite work + // if in a few lines we happen to delete this building. + + JobContainer jc = this.getTimers().get("UPGRADE"); + + if (jc != null) { + if (!JobScheduler.getInstance().cancelScheduledJob(jc)) + Logger.error("failed to cancel existing upgrade job."); + } + + // Attempt write to database, or delete the building + // if we are destroying it. + + if (newRank == -1) + success = DbManager.BuildingQueries.DELETE_FROM_DATABASE(this); + else + success = DbManager.BuildingQueries.updateBuildingRank(this, newRank); + + if (success == false) { + Logger.error("Error writing to database UUID: " + this.getObjectUUID()); + return; + } + + this.isDeranking.compareAndSet(false, true); + + // Change the building's rank + + this.rank = newRank; + + // New rank means new mesh + + newMeshUUID = this.getBlueprint().getMeshForRank(this.rank); + this.meshUUID = newMeshUUID; + + // New rank mean new max hitpoints. + + this.healthMax = this.getBlueprint().getMaxHealth(this.rank); + this.setCurrentHitPoints(this.healthMax); + + if (this.getUpgradeDateTime() != null) + BuildingManager.setUpgradeDateTime(this, null, 0); + + // If we destroyed this building make sure to turn off + // protection + + if (this.rank == -1) + this.protectionState = ProtectionState.NONE; + + if ((this.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) + && (this.rank == 8)) + this.meshUUID = Realm.getRealmMesh(this.getCity()); + ; + + // update object to clients + + this.refresh(true); + if (this.getBounds() != null) + this.getBounds().setBounds(this); + + // Cleanup hirelings resulting from rank change + + BuildingManager.cleanupHirelings(this); + + this.isDeranking.compareAndSet(true, false); + } + + public final int getOwnerUUID() { + return ownerUUID; + } + + public final boolean isOwnerIsNPC() { + return ownerIsNPC; + } + + public final City getCity() { + + if (this.parentZone == null) + return null; + + if (this.getBlueprint() != null && this.getBlueprint().isSiegeEquip() && this.protectionState.equals(ProtectionState.PROTECTED)) { + if (this.getGuild() != null) { + if (this.getGuild().getOwnedCity() != null) { + if (this.getLoc().isInsideCircle(this.getGuild().getOwnedCity().getLoc(), CityBoundsType.ZONE.extents)) + return this.getGuild().getOwnedCity(); + } else { + Bane bane = Bane.getBaneByAttackerGuild(this.getGuild()); + + if (bane != null) { + if (bane.getCity() != null) { + if (this.getLoc().isInsideCircle(bane.getCity().getLoc(), CityBoundsType.ZONE.extents)) + return bane.getCity(); + } + } + } + } + } + if (this.parentZone.isPlayerCity() == false) + return null; + + return City.getCity(this.parentZone.getPlayerCityUUID()); + + } + + public final String getCityName() { + + City city = getCity(); + + if (city != null) + return city.getName(); + + return ""; + } + + public final Blueprint getBlueprint() { + + if (this.blueprintUUID == 0) + return null; + + return Blueprint.getBlueprint(this.blueprintUUID); + + } + + public final int getBlueprintUUID() { + + return this.blueprintUUID; + } + + public final void setCurrentHitPoints(Float CurrentHitPoints) { + this.addDatabaseJob("health", MBServerStatics.ONE_MINUTE); + this.setHealth(CurrentHitPoints); + } + + //This method is to handle when a building is damaged below 0 health. + //Either destroy or derank it. + + public final LocalDateTime getUpgradeDateTime() { + lock.readLock().lock(); + try { + return upgradeDateTime; + } finally { + lock.readLock().unlock(); + } + } + + public final float modifyHealth(final float value, final AbstractCharacter attacker) { + + if (this.rank == -1) + return 0f; + + boolean worked = false; + Float oldHealth = 0f, newHealth = 0f; + while (!worked) { + if (this.rank == -1) + return 0f; + oldHealth = this.health.get(); + newHealth = oldHealth + value; + if (newHealth > this.healthMax) + newHealth = healthMax; + worked = this.health.compareAndSet(oldHealth, newHealth); + } + + if (newHealth < 0) { + if (this.isDeranking.compareAndSet(false, true)) { + this.destroyOrDerank(attacker); + } + + return newHealth - oldHealth; + } else + this.addDatabaseJob("health", MBServerStatics.ONE_MINUTE); + + if (value < 0) + Mine.SendMineAttackMessage(this); + + return newHealth - oldHealth; + + + } + + public final void destroyOrDerank(AbstractCharacter attacker) { + + Blueprint blueprint; + City city; + + // Sanity check: Early exit if a non + // blueprinted object is attempting to + // derank. + + if (this.blueprintUUID == 0) + return; + + blueprint = this.getBlueprint(); + city = this.getCity(); + + // Special handling of destroyed Banes + + if (blueprint.getBuildingGroup() == BuildingGroup.BANESTONE) { + city.getBane().endBane(SiegeResult.DEFEND); + return; + } + + // Special handling of warehouses + + if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) + if (city != null) + city.setWarehouseBuildingID(0); + + // Special handling of destroyed Spires + + if ((blueprint.getBuildingGroup() == BuildingGroup.SPIRE) && this.rank == 1) + this.disableSpire(true); + + // Special handling of destroyed Mines + + if (blueprint.getBuildingGroup() == BuildingGroup.MINE + && this.rank == 1) { + + Mine mine = Mine.getMineFromTower(this.getObjectUUID()); + + if (mine != null) { + + // Warehouse mine destruction event + + MineRecord mineRecord = MineRecord.borrow(mine, attacker, RecordEventType.DESTROY); + DataWarehouse.pushToWarehouse(mineRecord); + + this.setRank(-1); + this.setCurrentHitPoints((float) 1); + this.healthMax = (float) 1; + this.meshUUID = this.getBlueprint().getMeshForRank(this.rank); + mine.handleDestroyMine(); + this.getBounds().setBounds(this); + this.refresh(true); + return; + } + } + + // Special handling of deranking Trees + + if (blueprint.getBuildingGroup() == BuildingGroup.TOL) { + derankTreeOfLife(); + return; + } + + // If codepath reaches here then it's a regular + // structure not requiring special handling. + // Time to either derank or destroy the building. + + if ((this.rank - 1) < 1) + this.setRank(-1); + else + this.setRank(this.rank - 1); + + } + + // Return the maint cost in gold associated with this structure + + private void derankTreeOfLife() { + + City city; + Bane bane; + Realm cityRealm; + ArrayList spireBuildings = new ArrayList<>(); + ArrayList shrineBuildings = new ArrayList<>(); + ArrayList barracksBuildings = new ArrayList<>(); + Building spireBuilding; + Building shrineBuilding; + SiegeResult siegeResult; + AbstractCharacter newOwner; + + city = this.getCity(); + + if (city == null) { + Logger.error("No city for tree of uuid" + this.getObjectUUID()); + return; + } + + bane = city.getBane(); + + // We need to collect the spires and shrines on the citygrid in case + // they will be deleted as excess as the tree deranks. + + for (Building building : city.getParent().zoneBuildingSet) { + + //don't add -1 rank buildings. + + if (building.rank <= 0) + continue; + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) + spireBuildings.add(building); + + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) + shrineBuildings.add(building); + + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) + barracksBuildings.add(building); + } + + // A tree can only hold so many spires. As it deranks we need to delete + // the excess + + if (spireBuildings.size() > Blueprint.getMaxShrines(this.rank - 1)) { + + spireBuilding = spireBuildings.get(0); + + // Disable and delete a random spire + + if (spireBuilding != null) { + spireBuilding.disableSpire(true); + spireBuilding.setRank(-1); + } + } + + if (shrineBuildings.size() > Blueprint.getMaxShrines(this.rank - 1)) { + + shrineBuilding = shrineBuildings.get(0); + + // Delete a random shrine + + if (shrineBuilding != null) + shrineBuilding.setRank(-1); + } + + if (barracksBuildings.size() > this.rank - 1) { + + Building barracksBuilding = barracksBuildings.get(0); + + // Delete a random barrack + + if (barracksBuilding != null) + barracksBuilding.setRank(-1); + } + + // If the tree is R8 and deranking, we need to update it's + // mesh along with buildings losing their health bonus + + if (this.rank == 8) { + + cityRealm = city.getRealm(); + + if (cityRealm != null) + cityRealm.abandonRealm(); + + for (Building cityBuilding : this.parentZone.zoneBuildingSet) { + + if ((cityBuilding.getBlueprint() != null && cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.TOL) + && (cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE)) { + cityBuilding.healthMax = cityBuilding.getBlueprint().getMaxHealth(cityBuilding.rank); + } + + if (cityBuilding.health.get() > cityBuilding.healthMax) + cityBuilding.setHealth(cityBuilding.healthMax); + } + } + + // Tree is simply deranking. + // Let's do so and early exit + + if (this.rank > 1) { + this.setRank(rank - 1); + City.lastCityUpdate = System.currentTimeMillis(); + return; + } + + // Must remove a bane before considering destruction of a TOL + + if (bane != null) { + + // Cache the new owner + + newOwner = Guild.GetGL(bane.getOwner().getGuild()); + + this.isDeranking.compareAndSet(false, true); + + if ((bane.getOwner().getGuild().getGuildState() == GuildState.Sovereign) || + (bane.getOwner().getGuild().getGuildState() == GuildState.Protectorate) || + (bane.getOwner().getGuild().getGuildState() == GuildState.Province) || + (bane.getOwner().getGuild().getGuildState() == GuildState.Nation)) + siegeResult = SiegeResult.DESTROY; + else + siegeResult = SiegeResult.CAPTURE; + + // Remove realm if city had one + + Realm realm = RealmMap.getRealmAtLocation(city.getLoc()); + + if (realm != null) + if (realm.isRealmFullAfterBane()) + siegeResult = SiegeResult.DESTROY; + + city.getBane().endBane(siegeResult); + + // If it's a capture bane transfer the tree and exit + + if (siegeResult.equals(SiegeResult.CAPTURE)) { + city.transfer(newOwner); + CityRecord cityRecord = CityRecord.borrow(city, RecordEventType.CAPTURE); + DataWarehouse.pushToWarehouse(cityRecord); + return; + } + } // end removal of bane + + // if codepath reaches here then we can now destroy the tree and the city + + CityRecord cityRecord = CityRecord.borrow(city, RecordEventType.DESTROY); + DataWarehouse.pushToWarehouse(cityRecord); + + city.destroy(); + + } + + public float getCurrentHitpoints() { + return this.health.get(); + } + + public int getMaintCost() { + + int maintCost = 0; + + // Add cost for building structure + + maintCost += this.getBlueprint().getMaintCost(rank); + + // Add costs associated with hirelings + + for (AbstractCharacter npc : this.hirelings.keySet()) { + + if (npc.getObjectType() != GameObjectType.NPC) + continue; + + + maintCost += Blueprint.getNpcMaintCost(npc.getRank()); + } + + return maintCost; + } + + public final void submitOpenDoorJob(int doorID) { + + //cancel any outstanding door close jobs for this door + + if (this.doorJobs.containsKey(doorID)) { + this.doorJobs.get(doorID).cancelJob(); + this.doorJobs.remove(doorID); + } + + //add new door close job + + DoorCloseJob dcj = new DoorCloseJob(this, doorID); + this.doorJobs.put(doorID, dcj); + JobScheduler.getInstance().scheduleJob(dcj, MBServerStatics.DOOR_CLOSE_TIMER); + } + + public final float getMaxHitPoints() { + return this.healthMax; + } + + public final void setMaxHitPoints(float maxHealth) { + this.healthMax = maxHealth; + } + + public final void setw(float value) { + this.w = value; + } + + public final float getw() { + return this.w; + } + + public final Vector3f getMeshScale() { + return this.meshScale; + } + + public final int getMeshUUID() { + return this.meshUUID; + } + + public final void setMeshUUID(int value) { + this.meshUUID = value; + } + + public final Resists getResists() { + return this.resists; + } + + public final Zone getParentZone() { + return this.parentZone; + } + + //Sets the relative position to a parent zone + + public final void setParentZone(Zone zone) { + + //update ZoneManager's zone building list + if (zone != null) + if (this.parentZone != null) { + + this.parentZone.zoneBuildingSet.remove(this); + if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) { + this.RemoveFromBarracksList(); + } + zone.zoneBuildingSet.add(this); + + } else + zone.zoneBuildingSet.add(this); + else if (this.parentZone != null) { + this.parentZone.zoneBuildingSet.remove(this); + if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) { + this.RemoveFromBarracksList(); + } + } + if (this.parentZone == null) { + this.parentZone = zone; + this.setLoc(new Vector3fImmutable(this.statLat + zone.absX, this.statAlt + zone.absY, this.statLon + zone.absZ)); + } else + this.parentZone = zone; + if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) { + AddToBarracksList(); + } + } + + public final int getParentZoneID() { + + if (this.parentZone == null) + return 0; + + return this.parentZone.getObjectUUID(); + } + + public float getStatLat() { + return statLat; + } + + public float getStatLon() { + return statLon; + } + + public float getStatAlt() { + return statAlt; + } + + public Guild getGuild() { + + AbstractCharacter buildingOwner; + + buildingOwner = this.getOwner(); + + if (buildingOwner != null) + return buildingOwner.getGuild(); + else + return Guild.getErrantGuild(); + } + + public int getEffectFlags() { + return this.effectFlags; + } + + public void addEffectBit(int bit) { + this.effectFlags |= bit; + } + + public void removeAllVisualEffects() { + this.effectFlags = 0; + ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(3276859, 1, this.getObjectType().ordinal(), this.getObjectUUID(), 0); + DispatchMessage.sendToAllInRange(this, applyBuildingEffectMsg); + } + + /* + * Utils + */ + + public void removeEffectBit(int bit) { + this.effectFlags &= (~bit); + + } + + @Override + public String getName() { + return this.name; + } + + public final void setName(String value) { + + if (DbManager.BuildingQueries.CHANGE_NAME(this, value) == false) + return; + + this.name = value; + this.updateName(); + } + + + /* + * Serializing + */ + + public final AbstractCharacter getOwner() { + + if (this.ownerUUID == 0) + return null; + if (this.ownerIsNPC) + return NPC.getFromCache(this.ownerUUID); + + return PlayerCharacter.getFromCache(this.ownerUUID); + + } + + /* + * Database + */ + + public final String getOwnerName() { + AbstractCharacter owner = this.getOwner(); + if (owner != null) + return owner.getName(); + return ""; + } + + public final String getGuildName() { + Guild g = getGuild(); + if (g != null) + return g.getName(); + return "None"; + } + + @Override + public void updateDatabase() { + + // *** Refactor : Log error here to see if it's ever called + } + + public final LocalDateTime getDateToUpgrade() { + return upgradeDateTime; + } + + public final boolean setStrongboxValue(int newValue) { + + boolean success = true; + + try { + DbManager.BuildingQueries.SET_PROPERTY(this, "currentGold", newValue); + this._strongboxValue = newValue; + } catch (Exception e) { + success = false; + Logger.error("Error writing to database"); + } + + return success; + } + + public final int getStrongboxValue() { + return _strongboxValue; + } + + public final void refresh(boolean newMesh) { + + if (newMesh) + WorldGrid.updateObject(this); + else { + UpdateObjectMsg uom = new UpdateObjectMsg(this, 3); + DispatchMessage.sendToAllInRange(this, uom); + } + } + + public final void updateName() { + + UpdateObjectMsg uom = new UpdateObjectMsg(this, 2); + DispatchMessage.sendToAllInRange(this, uom); + + } + + // *** Refactor: Can't we just use setRank() for this? + + public final void rebuildMine() { + this.setRank(1); + this.meshUUID = this.getBlueprint().getMeshForRank(this.rank); + + // New rank mean new max hitpoints. + + this.healthMax = this.getBlueprint().getMaxHealth(this.rank); + this.setCurrentHitPoints(this.healthMax); + this.getBounds().setBounds(this); + } + + public final void refreshGuild() { + + UpdateObjectMsg uom = new UpdateObjectMsg(this, 5); + DispatchMessage.sendToAllInRange(this, uom); + + } + + public int getMaxGold() { + return maxGold; + } + + //This returns if a player is allowed access to control the building + + @Override + public void runAfterLoad() { + + try { + + this.parentZone.zoneBuildingSet.add(this); + + // Submit upgrade job if building is currently set to rank. + + try { + DbObjectType objectType = DbManager.BuildingQueries.GET_UID_ENUM(this.ownerUUID); + this.ownerIsNPC = (objectType == DbObjectType.NPC); + } catch (Exception e) { + this.ownerIsNPC = false; + Logger.error("Failed to find Object Type for owner " + this.ownerUUID + " Location " + this.getLoc().toString()); + } + + try { + DbManager.BuildingQueries.LOAD_ALL_FRIENDS_FOR_BUILDING(this); + DbManager.BuildingQueries.LOAD_ALL_CONDEMNED_FOR_BUILDING(this); + } catch (Exception e) { + Logger.error(this.getObjectUUID() + " failed to load friends/condemned." + e.getMessage()); + } + + //LOad Owners in Cache so we do not have to continuely look in the db for owner. + + if (this.ownerIsNPC) { + if (NPC.getNPC(this.ownerUUID) == null) + Logger.info("Building UID " + this.getObjectUUID() + " Failed to Load NPC Owner with ID " + this.ownerUUID + " Location " + this.getLoc().toString()); + + } else if (this.ownerUUID != 0) { + if (PlayerCharacter.getPlayerCharacter(this.ownerUUID) == null) { + Logger.info("Building UID " + this.getObjectUUID() + " Failed to Load Player Owner with ID " + this.ownerUUID + " Location " + this.getLoc().toString()); + } + } + + // Apply health bonus and special mesh for realm if applicable + if ((this.getCity() != null) && this.getCity().getTOL() != null && (this.getCity().getTOL().rank == 8)) { + + // Update mesh accordingly + if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) + this.meshUUID = Realm.getRealmMesh(this.getCity()); + + // Apply realm capital health bonus. + // Do not apply bonus to banestones or TOL's. *** Refactor: + // Possibly only protected buildings? Needs some thought. + + float missingHealth = 0; + + if (this.health.get() != 0) + missingHealth = this.healthMax - this.health.get(); + + if ((this.getBlueprint() != null && this.getBlueprint().getBuildingGroup() != BuildingGroup.TOL) + && (this.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE)) { + this.healthMax += (this.healthMax * Realm.getRealmHealthMod(this.getCity())); + + if (this.health.get() != 0) + this.health.set(this.healthMax - missingHealth); + + if (this.health.get() > this.healthMax) + this.health.set(this.healthMax); + } + } + + // Set bounds for this building + + Bounds buildingBounds = Bounds.borrow(); + buildingBounds.setBounds(this); + this.setBounds(buildingBounds); + + //create a new list for children if the building is not a child. children list default is null. + //TODO Remove Furniture/Child buildings from building class and move them into a seperate class. + + if (this.parentBuildingID == 0) + this.children = new ArrayList<>(); + + if (this.parentBuildingID != 0) { + Building parent = BuildingManager.getBuildingFromCache(this.parentBuildingID); + + if (parent != null) { + parent.children.add(this); + + //add furniture to region cache. floor and level are reversed in database, //TODO Fix + + Regions region = BuildingManager.GetRegion(parent, this.level, this.floor, this.getLoc().x, this.getLoc().z); + if (region != null) + Regions.FurnitureRegionMap.put(this.getObjectUUID(), region); + } + + } + + if (this.upgradeDateTime != null) + BuildingManager.submitUpgradeJob(this); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public synchronized boolean setOwner(AbstractCharacter newOwner) { + + int newOwnerID; + if (newOwner == null) + newOwnerID = 0; + else + newOwnerID = newOwner.getObjectUUID(); + + try { + + // Save new owner to database + + if (!DbManager.BuildingQueries.updateBuildingOwner(this, newOwnerID)) + return false; + + if (newOwner == null) { + this.ownerIsNPC = false; + this.ownerUUID = 0; + } else { + this.ownerUUID = newOwner.getObjectUUID(); + this.ownerIsNPC = (newOwner.getObjectType() == GameObjectType.NPC); + } + + + // Set new guild for hirelings and refresh all clients + + this.refreshGuild(); + BuildingManager.refreshHirelings(this); + + } catch (Exception e) { + Logger.error("Error updating owner! UUID: " + this.getObjectUUID()); + return false; + } + + return true; + + } + + //This turns on and off low damage effect for building + + public void toggleDamageLow(boolean on) { + if (on) + addEffectBit(2); + else + removeEffectBit(2); + } + + //This turns on and off medium damage effect for building + + public void toggleDamageMedium(boolean on) { + if (on) + addEffectBit(4); + else + removeEffectBit(4); + } + + //This turns on and off high damage effect for building + + public void toggleDamageHigh(boolean on) { + if (on) + addEffectBit(8); + else + removeEffectBit(8); + } + + //This clears all damage effects on a building + public void clearDamageEffect() { + toggleDamageLow(false); + toggleDamageMedium(false); + toggleDamageHigh(false); + } + + public Vector3fImmutable getStuckLocation() { + Vector3fImmutable stuckLocation; + ArrayList stuckLocations; + + stuckLocations = BuildingManager._stuckLocations.get(this.meshUUID); + + // Sanity check + + if (stuckLocations == null || + stuckLocations.isEmpty()) + return this.getLoc(); + + stuckLocation = stuckLocations.get(ThreadLocalRandom.current().nextInt(stuckLocations.size())).getLocation(); + stuckLocation = this.getLoc().add(stuckLocation); + + // Rotate stuck position by the building rotation + + stuckLocation = Vector3fImmutable.rotateAroundPoint(this.getLoc(), stuckLocation, this.getBounds().getQuaternion().angleY); + + return stuckLocation; + } + + public boolean isDoorOpen(int doorNumber) { + + if (this.doorState == 0) + return false; + + return (this.doorState & (1 << doorNumber + 16)) != 0; + + } + + public boolean isDoorLocked(int doorNumber) { + + if (this.doorState == 0) + return false; + + return (this.doorState & (1 << doorNumber)) != 0; + + } + + public boolean setDoorState(int doorNumber, DoorState doorState) { + + boolean updateRecord; + + updateRecord = false; + + // Can't have an invalid door number + // Log error? + + if (doorNumber < 1 || doorNumber > 16) + return false; + + switch (doorState) { + + case OPEN: + this.doorState |= (1 << (doorNumber + 16)); + break; + case CLOSED: + this.doorState &= ~(1 << (doorNumber + 16)); + break; + case UNLOCKED: + this.doorState &= ~(1 << doorNumber); + updateRecord = true; + break; + case LOCKED: + this.doorState |= (1 << doorNumber); + updateRecord = true; + break; + } + + // Save to database ? + if (updateRecord == true) + return DbManager.BuildingQueries.UPDATE_DOOR_LOCK(this.getObjectUUID(), this.doorState); + else + return true; + } + + public void updateEffects() { + + ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(0x00720063, 1, this.getObjectType().ordinal(), this.getObjectUUID(), this.effectFlags); + DispatchMessage.sendToAllInRange(this, applyBuildingEffectMsg); + + } + + public final void enableSpire() { + + SpireType spireType; + + if (this.getCity() == null) + return; + + // Blueprint sanity check + + if (this.blueprintUUID == 0) + return; + + spireType = SpireType.getByBlueprintUUID(this.blueprintUUID); + + SiegeSpireWithdrawlJob spireJob = new SiegeSpireWithdrawlJob(this); + JobContainer jc = JobScheduler.getInstance().scheduleJob(spireJob, 300000); + this.getTimers().put("SpireWithdrawl", jc); + + this.getCity().addCityEffect(spireType.getEffectBase(), rank); + addEffectBit(spireType.getEffectFlag()); + this.spireIsActive = true; + this.updateEffects(); + + + } + + public final void disableSpire(boolean refreshEffect) { + + SpireType spireType; + + if (this.getCity() == null) + return; + + // Blueprint sanity check + + if (this.blueprintUUID == 0) + return; + + spireType = SpireType.getByBlueprintUUID(this.blueprintUUID); + + this.getCity().removeCityEffect(spireType.getEffectBase(), rank, refreshEffect); + + JobContainer toRemove = this.getTimers().get("SpireWithdrawl"); + + if (toRemove != null) { + toRemove.cancelJob(); + this.getTimers().remove("SpireWithdrawl"); + } + + this.spireIsActive = false; + this.removeEffectBit(spireType.getEffectFlag()); + this.updateEffects(); + } + + public ConcurrentHashMap getHirelings() { + return hirelings; + } + + public final boolean isSpireIsActive() { + return spireIsActive; + } + + public final void setSpireIsActive(boolean spireIsActive) { + this.spireIsActive = spireIsActive; + } + + public final boolean isVulnerable() { + + // NPC owned buildings are never vulnerable + + if (ownerIsNPC) + return false; + + // Buildings on an npc citygrid are never vulnerable + + if (this.getCity() != null) { + if (this.getCity().getParent().isNPCCity() == true) + return false; + } + + // Destroyed buildings are never vulnerable + + if (rank < 0) + return false; + + // Any structure without a blueprint was not placed by a + // player and we can assume to be invulnerable regardless + // of a protection contract or not. + + if (this.getBlueprint() == null) + return false; + + // Runegates are never vulerable. + + if (this.getBlueprint().getBuildingGroup() == BuildingGroup.RUNEGATE) + return false; + + // Shrines are never vulerable. They blow up as a + // tree deranks. + + if (this.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) + return false; + + // Mines are vulnerable only if they are active + + if (this.getBlueprint().getBuildingGroup() == BuildingGroup.MINE) { + + // Cannot access mine + + if (Mine.getMineFromTower(this.getObjectUUID()) == null) + return false; + + return Mine.getMineFromTower(this.getObjectUUID()).getIsActive() == true; + } + + // Errant banestones are vulnerable by default + + if ((this.getBlueprint().getBuildingGroup() == BuildingGroup.BANESTONE) && + this.getCity().getBane().isErrant() == true) + return true; + + // There is an active protection contract. Is there also + // an active bane? If so, it's meaningless. + + if (this.assetIsProtected() == true) { + + // Building protection is meaningless without a city + + if (this.getCity() == null) + return true; + + // All buildings are vulnerable during an active bane + + return (this.getCity().protectionEnforced == false); + + } + + // No protection contract? Oh well, you're vunerable! + + return true; + } + + public final ConcurrentHashMap getTimers() { + if (this.timers == null) + this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + return this.timers; + } + + public final ConcurrentHashMap getTimestamps() { + if (this.timestamps == null) + this.timestamps = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + return this.timestamps; + } + + public final long getTimeStamp(final String name) { + if (this.getTimestamps().containsKey(name)) + return this.timestamps.get(name); + return 0L; + } + + public final void setTimeStamp(final String name, final long value) { + this.getTimestamps().put(name, value); + } + + public ConcurrentHashMap getFriends() { + return this.friends; + } + + public final void claim(AbstractCharacter sourcePlayer) { + + // Clear any existing friend or condemn entries + + this.friends.clear(); + DbManager.BuildingQueries.CLEAR_FRIENDS_LIST(this.getObjectUUID()); + + condemned.clear(); + DbManager.BuildingQueries.CLEAR_CONDEMNED_LIST(this.getObjectUUID()); + + // Transfer the building asset ownership + + this.setOwner(sourcePlayer); + + } + + /** + * @return the protectionState + */ + public ProtectionState getProtectionState() { + return protectionState; + } + + /** + * @param protectionState the protectionState to set + */ + public void setProtectionState(ProtectionState protectionState) { + + // Early exit if protection state is already set to input value + + if (this.protectionState.equals(protectionState)) + return; + + // if building is destroyed, just set the protection state. There isn't a DB + // record to write anything to. + + if (rank == -1) { + this.protectionState = protectionState; + return; + } + + if (DbManager.BuildingQueries.UPDATE_PROTECTIONSTATE(this.getObjectUUID(), protectionState) == true) { + this.protectionState = protectionState; + return; + } + + Logger.error("Protection update failed for UUID: " + this.getObjectUUID() + "\n" + + this.getBlueprint().getName() + " From " + this.protectionState.name() + " To: " + protectionState.name()); + + } + + public ConcurrentHashMap getCondemned() { + return condemned; + } + + public boolean setReverseKOS(boolean reverseKOS) { + if (!DbManager.BuildingQueries.updateReverseKOS(this, reverseKOS)) + return false; + this.reverseKOS = reverseKOS; + return true; + } + + public boolean assetIsProtected() { + + boolean outValue = false; + + if (protectionState.equals(ProtectionState.PROTECTED)) + outValue = true; + + if (protectionState.equals(ProtectionState.CONTRACT)) + outValue = true; + + return outValue; + } + + public synchronized boolean transferGold(int amount, boolean tax) { + + if (amount < 0) + if (!this.hasFunds(-amount)) + return false; + + if (_strongboxValue + amount < 0) + return false; + + if (_strongboxValue + amount > maxGold) + return false; + + //Deduct Profit taxes. + if (tax) + if (taxType == TaxType.PROFIT && protectionState == ProtectionState.CONTRACT && amount > 0) + amount = this.payProfitTaxes(amount); + + + if (amount != 0) + return this.setStrongboxValue(_strongboxValue + amount); + return true; + } + + public synchronized int payProfitTaxes(int amount) { + + if (this.getCity() == null) + return amount; + if (this.getCity().getWarehouse() == null) + return amount; + + if (this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()) >= Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID())) + return amount; + + int profitAmount = (int) (amount * (taxAmount * .01f)); + + if (this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()) + profitAmount <= Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID())) { + this.getCity().getWarehouse().depositProfitTax(ItemBase.getGoldItemBase(), profitAmount, this); + return amount - profitAmount; + } + //overDrafting + int warehouseDeposit = Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID()) - this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()); + this.getCity().getWarehouse().depositProfitTax(ItemBase.getGoldItemBase(), warehouseDeposit, this); + return amount - warehouseDeposit; + } + + public synchronized boolean setReserve(int amount, PlayerCharacter player) { + + if (!BuildingManager.playerCanManageNotFriends(player, this)) + return false; + + if (amount < 0) + return false; + + if (!DbManager.BuildingQueries.SET_RESERVE(this, amount)) + return false; + + this.reserve = amount; + + return true; + } + + public synchronized boolean hasFunds(int amount) { + return amount <= (this._strongboxValue - reserve); + } + + public ArrayList getPatrolPoints() { + return patrolPoints; + } + + public void setPatrolPoints(ArrayList patrolPoints) { + this.patrolPoints = patrolPoints; + } + + public ArrayList getSentryPoints() { + return sentryPoints; + } + + public void setSentryPoints(ArrayList sentryPoints) { + this.sentryPoints = sentryPoints; + } + + public synchronized boolean addProtectionTax(Building building, PlayerCharacter pc, final TaxType taxType, int amount, boolean enforceKOS) { + if (building == null) + return false; + + if (this.getBlueprint() == null) + return false; + + if (this.getBlueprint().getBuildingGroup() != BuildingGroup.TOL) + return false; + + if (building.assetIsProtected()) + return false; + + if (!DbManager.BuildingQueries.addTaxes(building, taxType, amount, enforceKOS)) + return false; + + building.taxType = taxType; + building.taxAmount = amount; + building.enforceKOS = enforceKOS; + + return true; + + } + + public synchronized boolean declineTaxOffer() { + return true; + } + + public synchronized boolean acceptTaxOffer() { + return true; + } + + public synchronized boolean acceptTaxes() { + + if (!DbManager.BuildingQueries.acceptTaxes(this)) + return false; + + this.setProtectionState(Enum.ProtectionState.CONTRACT); + this.taxDateTime = LocalDateTime.now().plusDays(7); + + return true; + } + + public synchronized boolean removeTaxes() { + + if (!DbManager.BuildingQueries.removeTaxes(this)) + return false; + + this.taxType = TaxType.NONE; + this.taxAmount = 0; + this.taxDateTime = null; + this.enforceKOS = false; + + return true; + } + + public boolean isTaxed() { + if (this.taxType == TaxType.NONE) + return false; + if (this.taxAmount == 0) + return false; + return this.taxDateTime != null; + } + + public void AddToBarracksList() { + City playerCity = ZoneManager.getCityAtLocation(this.loc); + if (playerCity != null) { + playerCity.cityBarracks.add(this); + } + } + + public void RemoveFromBarracksList() { + + } +} diff --git a/src/engine/objects/BuildingFriends.java b/src/engine/objects/BuildingFriends.java new file mode 100644 index 00000000..b5308978 --- /dev/null +++ b/src/engine/objects/BuildingFriends.java @@ -0,0 +1,53 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class BuildingFriends { + + private int playerUID; + private int buildingUID; + private int guildUID; + private int friendType; + + /** + * ResultSet Constructor + */ + + public BuildingFriends(ResultSet rs) throws SQLException { + this.playerUID = rs.getInt("playerUID"); + this.buildingUID = rs.getInt("buildingUID"); + this.guildUID = rs.getInt("guildUID"); + this.friendType = rs.getInt("friendType"); + } + + public BuildingFriends(int playerUID, int buildingUID, int guildUID, int friendType) { + super(); + this.playerUID = playerUID; + this.buildingUID = buildingUID; + this.guildUID = guildUID; + this.friendType = friendType; + } + + public int getPlayerUID() { + return playerUID; + } + + public int getGuildUID() { + return guildUID; + } + + public int getFriendType() { + return friendType; + } + +} diff --git a/src/engine/objects/BuildingLocation.java b/src/engine/objects/BuildingLocation.java new file mode 100644 index 00000000..214d89db --- /dev/null +++ b/src/engine/objects/BuildingLocation.java @@ -0,0 +1,126 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.math.Quaternion; +import engine.math.Vector3fImmutable; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + + +public class BuildingLocation extends AbstractGameObject { + + private final int buildingUUID; + private final int type; + private final int slot; + private final int unknown; + private final Vector3fImmutable location; + private final Quaternion rotation; + + + public BuildingLocation() { + + this.buildingUUID = 0; + this.type = 0; + this.slot = 0; + this.unknown = 0; + this.location = Vector3fImmutable.ZERO; + this.rotation = new Quaternion(); + } + + /** + * ResultSet Constructor + */ + public BuildingLocation(ResultSet rs) throws SQLException { + super(rs); + this.buildingUUID = rs.getInt("BuildingID"); + this.type = rs.getInt("type"); + this.slot = rs.getInt("slot"); + this.unknown = rs.getInt("unknown"); + this.location = new Vector3fImmutable(rs.getFloat("locX"), rs.getFloat("locY"), rs.getFloat("locZ")); + this.rotation = new Quaternion(rs.getFloat("rotX"), rs.getFloat("rotY"), rs.getFloat("rotZ"), rs.getFloat("w")); + } + + /* + * Getters + */ + + public static void loadBuildingLocations() { + + ArrayList buildingLocations = DbManager.BuildingLocationQueries.LOAD_BUILDING_LOCATIONS(); + HashMap> locationCollection = new HashMap<>(); + + // Only slot locations and stuck locations are currently loaded. + + for (BuildingLocation buildingLocation : buildingLocations) { + + switch (buildingLocation.type) { + case 6: + locationCollection = BuildingManager._slotLocations; + break; + case 8: + locationCollection = BuildingManager._stuckLocations; + break; + } + + // Add location to collection in BuildingManager + + if (locationCollection.containsKey(buildingLocation.buildingUUID)) + locationCollection.get(buildingLocation.buildingUUID).add(buildingLocation); + else { + locationCollection.put(buildingLocation.buildingUUID, new ArrayList<>()); + locationCollection.get(buildingLocation.buildingUUID).add(buildingLocation); + } + + } + } + + public int getBuildingUUID() { + return this.buildingUUID; + } + + public int getType() { + return this.type; + } + + public int getSlot() { + return this.slot; + } + + public int getUnknown() { + return this.unknown; + } + + public float getLocX() { + return this.location.x; + } + + public float getLocY() { + return this.location.y; + } + + public Vector3fImmutable getLocation() { + return this.location; + } + + public Quaternion getRotation() { + return this.rotation; + } + + @Override + public void updateDatabase() { + } + +} diff --git a/src/engine/objects/BuildingRegions.java b/src/engine/objects/BuildingRegions.java new file mode 100644 index 00000000..e19b0185 --- /dev/null +++ b/src/engine/objects/BuildingRegions.java @@ -0,0 +1,310 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; +import engine.math.Vector3f; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class BuildingRegions { + + + public static HashMap> _staticRegions = new HashMap<>(); + private final boolean exitRegion; + private final boolean stairs; + public Vector3f center; + private int buildingID; + private int level; + private int numVertex; + private float vertex1X; + private float vertex1Y; + private float vertex1Z; + private float vertex2X; + private float vertex2Y; + private float vertex2Z; + private float vertex3X; + private float vertex3Y; + private float vertex3Z; + private float vertex4X; + private float vertex4Y; + private float vertex4Z; + private byte ground1; + private byte ground2; + private byte ground3; + private byte ground4; + private short contentBehavior; + private boolean outside; + private float centerX; + private float centerZ; + private int room = 0; + private ArrayList regionPoints = new ArrayList<>(); + + /** + * ResultSet Constructor + */ + + public BuildingRegions(ResultSet rs) throws SQLException { + + buildingID = rs.getInt("buildingID"); + level = rs.getInt("level"); + room = rs.getInt("room"); + numVertex = rs.getInt("numVertex"); + vertex1X = rs.getFloat("vertex1X"); + vertex1Y = rs.getFloat("vertex1Y"); + vertex1Z = rs.getFloat("vertex1Z"); + vertex2X = rs.getFloat("vertex2X"); + vertex2Y = rs.getFloat("vertex2Y"); + vertex2Z = rs.getFloat("vertex2Z"); + vertex3X = rs.getFloat("vertex3X"); + vertex3Y = rs.getFloat("vertex3Y"); + vertex3Z = rs.getFloat("vertex3Z"); + vertex4X = rs.getFloat("vertex4X"); + vertex4Y = rs.getFloat("vertex4Y"); + vertex4Z = rs.getFloat("vertex4Z"); + + regionPoints.add(new Vector3f(vertex1X, vertex1Y, vertex1Z)); + regionPoints.add(new Vector3f(vertex2X, vertex2Y, vertex2Z)); + regionPoints.add(new Vector3f(vertex3X, vertex3Y, vertex3Z)); + + + if (numVertex == 4) + regionPoints.add(new Vector3f(vertex4X, vertex4Y, vertex4Z)); + + + this.contentBehavior = (rs.getShort("unknown_Order1")); + short state = rs.getShort("unknown_Order2"); + + if (state == 2) + this.outside = (true); + else + this.outside = (false); + + this.exitRegion = rs.getBoolean("colOrder1"); + this.stairs = rs.getBoolean("colOrder2"); + + + ground1 = rs.getByte("colOrder1"); + ground2 = rs.getByte("colOrder2"); + ground3 = rs.getByte("colOrder3"); + ground4 = rs.getByte("colOrder4"); + + float centerY = rs.getFloat("unknown_VectorY"); + centerX = rs.getFloat("unknown_VectorX"); + centerZ = rs.getFloat("unknown_VectorZ"); + + this.center = new Vector3f(centerX, centerY, centerZ); + + + } + + public static HashMap> get_staticRegions() { + return _staticRegions; + } + + public static void set_staticRegions(HashMap> _staticRegions) { + BuildingRegions._staticRegions = _staticRegions; + } + + public static void loadAllStaticColliders() { + _staticRegions = DbManager.BuildingQueries.LOAD_BUILDING_REGIONS(); + } + + public static ArrayList GetStaticCollidersForMeshID(int meshID) { + return _staticRegions.get(meshID); + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public int getNumVertex() { + return numVertex; + } + + public void setNumVertex(int numVertex) { + this.numVertex = numVertex; + } + + public float getVertex1X() { + return vertex1X; + } + + public void setVertex1X(float vertex1x) { + vertex1X = vertex1x; + } + + public float getVertex1Y() { + return vertex1Y; + } + + public void setVertex1Y(float vertex1y) { + vertex1Y = vertex1y; + } + + public float getVertex1Z() { + return vertex1Z; + } + + public void setVertex1Z(float vertex1z) { + vertex1Z = vertex1z; + } + + public float getVertex2X() { + return vertex2X; + } + + public void setVertex2X(float vertex2x) { + vertex2X = vertex2x; + } + + public float getVertex2Y() { + return vertex2Y; + } + + public void setVertex2Y(float vertex2y) { + vertex2Y = vertex2y; + } + + public float getVertex2Z() { + return vertex2Z; + } + + public void setVertex2Z(float vertex2z) { + vertex2Z = vertex2z; + } + + public float getVertex3X() { + return vertex3X; + } + + public void setVertex3X(float vertex3x) { + vertex3X = vertex3x; + } + + public float getVertex3Y() { + return vertex3Y; + } + + public void setVertex3Y(float vertex3y) { + vertex3Y = vertex3y; + } + + public float getVertex3Z() { + return vertex3Z; + } + + public void setVertex3Z(float vertex3z) { + vertex3Z = vertex3z; + } + + public float getVertex4X() { + return vertex4X; + } + + public void setVertex4X(float vertex4x) { + vertex4X = vertex4x; + } + + public float getVertex4Y() { + return vertex4Y; + } + + public void setVertex4Y(float vertex4y) { + vertex4Y = vertex4y; + } + + public float getVertex4Z() { + return vertex4Z; + } + + public void setVertex4Z(float vertex4z) { + vertex4Z = vertex4z; + } + + public boolean isGroundLevel() { + if (this.level > 0) + return false; + + if (this.ground1 == 0) + return true; + if (this.ground2 == 0) + return true; + if (this.ground3 == 0) + return true; + return this.ground4 == 0; + + } + + + public float getCenterX() { + return centerX; + } + + + public void setCenterX(float centerX) { + this.centerX = centerX; + } + + + public float getCenterY() { + return centerZ; + } + + + public void setCenterY(float centerY) { + this.centerZ = centerY; + } + + + public boolean isOutside() { + return outside; + } + + public short getContentBehavior() { + return contentBehavior; + } + + + public int getRoom() { + return room; + } + + + public ArrayList getRegionPoints() { + return regionPoints; + } + + + public boolean isExitRegion() { + return exitRegion; + } + + + public boolean isStairs() { + return stairs; + } + +} diff --git a/src/engine/objects/CharacterItemManager.java b/src/engine/objects/CharacterItemManager.java new file mode 100644 index 00000000..b4b6fcfa --- /dev/null +++ b/src/engine/objects/CharacterItemManager.java @@ -0,0 +1,2596 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.Enum.ItemType; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.ClientMessagePump; +import engine.net.client.msg.*; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; +import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup; + + +public class CharacterItemManager { + + private static final byte inventoryVer = (byte) 0; + private static final byte bankVer = (byte) 0; + private static final byte vaultVer = (byte) 0; + private final AbstractCharacter absCharacter; + // Mapping of all the items associated with this Manager + private final ConcurrentHashMap itemIDtoType = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + // Mapping of all items equipped in this Manager + // Key = Item Slot + private final ConcurrentHashMap equipped = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final HashSet inventory = new HashSet<>(); + private final HashSet bank = new HashSet<>(); + private final HashSet vault = new HashSet<>(); + private final HashSet equipOrder = new HashSet<>(); + public Item goldVault; + private Account account; + private Item goldInventory; + private Item goldBank; + private boolean bankOpened; + private boolean vaultOpened; + private short bankWeight; + private short inventoryWeight; + private short equipWeight; + private short vaultWeight; + private ClientConnection tradingWith; + private byte tradeCommitted; + private boolean tradeSuccess; + private HashSet trading; + private int goldTradingAmount; + private int tradeID = 0; + /* + * Item Manager Version data + */ + private byte equipVer = (byte) 0; + + public CharacterItemManager(AbstractCharacter ac) { + super(); + this.absCharacter = ac; + } + + public static byte getInventoryVer() { + return inventoryVer; + } + + public static byte getBankVer() { + return bankVer; + } + + public static byte getVaultVer() { + return vaultVer; + } + + public static void takeFromNPC(NPC npc, PlayerCharacter pc, Item take, ClientMessagePump clientMessagePump) { + ItemBase ib = take.getItemBase(); + if (ib == null) + return; + CharacterItemManager itemMan = pc.getCharItemManager(); + if (itemMan == null) + return; + CharacterItemManager npcCim = npc.getCharItemManager(); + if (npcCim == null) + return; + if (!npcCim.inventoryContains(take)) { + return; + } + + if (!itemMan.hasRoomInventory(ib.getWeight())) + return; + if (take != null) { + itemMan.buyFromNPC(take, npc); + itemMan.updateInventory(); + } + } + + public void load() { + loadForGeneric(); + + if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + loadForPlayerCharacter(); + else if (this.absCharacter.getObjectType().equals(GameObjectType.NPC)) + loadForNPC(); + + } + + public void loadGoldItems() { + + if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) { + //other server, just make generic + this.goldInventory = new MobLoot(this.absCharacter, 0); + this.goldBank = new MobLoot(this.absCharacter, 0); + this.goldVault = new MobLoot(this.absCharacter, 0); + return; + } + + //create inventory gold if needed + if (this.goldInventory == null) + if (this.absCharacter != null && (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) || this.absCharacter.getObjectType().equals(GameObjectType.NPC))) + this.goldInventory = Item.newGoldItem(this.absCharacter, ItemBase.getItemBase(7), Enum.ItemContainerType.INVENTORY); + else + this.goldInventory = new MobLoot(this.absCharacter, 0); + + //create bank gold if needed + if (this.goldBank == null) + if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + this.goldBank = Item.newGoldItem(this.absCharacter, ItemBase.getItemBase(7), Enum.ItemContainerType.BANK); + else + this.goldBank = new MobLoot(this.absCharacter, 0); + + //create vault gold if needed + if (this.goldVault == null) + if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) { + this.goldVault = this.account.vaultGold; + } else + this.goldVault = new MobLoot(this.absCharacter, 0); + + this.itemIDtoType.put(this.goldInventory.getObjectUUID(), this.goldInventory.getObjectType().ordinal()); + this.itemIDtoType.put(this.goldBank.getObjectUUID(), this.goldBank.getObjectType().ordinal()); + this.itemIDtoType.put(this.goldVault.getObjectUUID(), this.goldVault.getObjectType().ordinal()); + + } + + private void loadForPlayerCharacter() { + ArrayList al = null; + + // TODO Verify this is an actual account. + this.account = ((PlayerCharacter) this.absCharacter).getAccount(); + + // Get Items for player and vault + if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) //login, only need equipped items + al = DbManager.ItemQueries.GET_EQUIPPED_ITEMS(this.absCharacter.getObjectUUID()); + else + al = DbManager.ItemQueries.GET_ITEMS_FOR_PC(this.absCharacter.getObjectUUID()); + + for (Item i : al) { + + i.validateItemContainer(); + this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal()); + + switch (i.containerType) { + case EQUIPPED: + if (this.equipped.containsValue(i) == false) { + this.equipped.put((int) i.getEquipSlot(), i); + addEquipOrder((int) i.getEquipSlot()); + } + break; + case BANK: + if (i.getItemBase().getType().equals(ItemType.GOLD)) + this.goldBank = i; + else if (this.bank.contains(i) == false) + this.bank.add(i); + break; + case INVENTORY: + if (i.getItemBase().getType().equals(ItemType.GOLD)) + this.goldInventory = i; + else if (this.inventory.contains(i) == false) + this.inventory.add(i); + break; + case VAULT: + if (i.getItemBase().getType().equals(ItemType.GOLD)) + this.goldVault = i; + else if (this.vault.contains(i) == false) + this.vault.add(i); + break; + default: + i.junk(); + break; + } + + } + + this.goldVault = this.account.vaultGold; + + //check all gold is created + //loadGoldItems(); + calculateWeights(); + } + + private void loadForNPC() { + ArrayList al = null; + + // Get all items related to this NPC: + al = DbManager.ItemQueries.GET_ITEMS_FOR_NPC(this.absCharacter.getObjectUUID()); + + for (Item i : al) { + i.validateItemContainer(); + this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal()); + + switch (i.containerType) { + case EQUIPPED: + if (this.equipped.containsValue(i) == false) + this.equipped.put((int) i.getEquipSlot(), i); + break; + case BANK: + if (i.getItemBase().getType().equals(ItemType.GOLD)) + this.goldBank = i; + else if (this.bank.contains(i) == false) + this.bank.add(i); + break; + case INVENTORY: + if (i.getItemBase().getType().equals(ItemType.GOLD)) + this.goldInventory = i; + else if (this.inventory.contains(i) == false) + this.inventory.add(i); + break; + default: + i.junk(); + break; + } + } + + //check all gold is created + //loadGoldItems(); + } + + private void loadForGeneric() { + this.bankWeight = 0; + this.inventoryWeight = 0; + this.equipWeight = 0; + this.vaultWeight = 0; + + //check all gold is created + //loadGoldItems(); + // Always initialize with bank and vault closed + bankOpened = false; + vaultOpened = false; + } + + //Positve Amount = TO BUILDING; Negative Amount = FROM BUILDING. flip signs for Player inventory. + public synchronized boolean transferGoldToFromBuilding(int amount, AbstractWorldObject object) { + if (this.absCharacter.getObjectType() != GameObjectType.PlayerCharacter) + return false; + + PlayerCharacter player = (PlayerCharacter) this.absCharacter; + + switch (object.getObjectType()) { + case Building: + Building building = (Building) object; + + if (!this.getGoldInventory().validForInventory(player.getClientConnection(), player, this)) + return false; + + if (amount < 0 && amount > building.getStrongboxValue()) + return false; + + // Not enough gold in inventory to transfer to tree + + if ((amount > 0) && + (this.getGoldInventory().getNumOfItems() - amount < 0)) { + sendErrorPopup(player, 28); + return false; + } + + if (this.getGoldInventory().getNumOfItems() - amount > MBServerStatics.PLAYER_GOLD_LIMIT) { + ErrorPopupMsg.sendErrorPopup(player, 202); + return false; + } + + + // Not enough gold to transfer to inventory from tree + + if ((amount < 0) && + (building.getStrongboxValue() + amount < 0)) { + sendErrorPopup(player, 127); + return false; + } + + if (amount < 0) + if (!building.hasFunds(-amount)) + return false; + + //Verify player can access building to transfer goldItem + + if (!BuildingManager.playerCanManage(player, building)) + return false; + + if (building.getStrongboxValue() + amount > building.getMaxGold()) { + ErrorPopupMsg.sendErrorPopup(player, 201); + return false; + } + + if (this.getOwner().getCharItemManager().getGoldTrading() > 0) { + if (this.getOwner().getObjectType().equals(GameObjectType.PlayerCharacter)) + ErrorPopupMsg.sendErrorPopup((PlayerCharacter) this.getOwner(), 195); + return false; + } + + if (!this.modifyInventoryGold(-amount)) { + + Logger.error(player.getName() + " transfer amount = " + amount + " ; Gold Inventory = " + this.getGoldInventory().getNumOfItems()); + + // ChatManager.chatSystemError(player, "You do not have this Gold."); + return false; + } + + if (!building.transferGold(amount, false)) { + + Logger.error(player.getName() + " transfer amount = " + amount + " ; Gold Inventory = " + this.getGoldInventory().getNumOfItems() + "; Building Strongbox = " + building.getStrongboxValue()); + + //ChatManager.chatSystemError(player, "Something went terribly wrong. Contact CCR."); + return false; + } + + break; + case Warehouse: + + Warehouse warehouse = (Warehouse) object; + + if (amount < 0) { + if (!warehouse.deposit((PlayerCharacter) this.absCharacter, this.getGoldInventory(), amount * -1, true, true)) { + + ErrorPopupMsg.sendErrorPopup((PlayerCharacter) this.absCharacter, 203); + return false; + } + } else { + if (!warehouse.withdraw((PlayerCharacter) this.absCharacter, this.getGoldInventory().getItemBase(), amount * -1, true, true)) { + + ErrorPopupMsg.sendErrorPopup((PlayerCharacter) this.absCharacter, 203); + return false; + } + + } + + break; + + } + return true; + } + + /* + * Item Controls + */ + public synchronized boolean modifyInventoryGold(int modifyValue) { + + Item goldItem; + PlayerCharacter player; + boolean success = false; + + goldItem = getGoldInventory(); + + if (goldItem == null) { + Logger.error("ModifyInventoryGold", "Could not create gold item"); + return success; + } + + if (this.getGoldInventory().getNumOfItems() + modifyValue > MBServerStatics.PLAYER_GOLD_LIMIT) { + return false; + } + + if (this.getGoldInventory().getNumOfItems() + modifyValue < 0) + return false; + + // No database update for npc's gold values so we use the player object + // for flow control later on. + + if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter) + player = (PlayerCharacter) this.absCharacter; + else + player = null; + + // If this is an update for a player character update the database + + if (player != null) + try { + if (!DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(), this.goldInventory.getNumOfItems() + modifyValue)) { + return false; + } + + + success = true; + } catch (Exception e) { + Logger.error("ModifyInventoryGold", "Error writing to database"); + } + + // Update in-game gold values for character + goldItem.setNumOfItems(goldItem.getNumOfItems() + modifyValue); + UpdateGoldMsg ugm = new UpdateGoldMsg(this.absCharacter); + ugm.configure(); + + Dispatch dispatch = Dispatch.borrow(player, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return success; + } + + public synchronized boolean tradeRequest(TradeRequestMsg msg) { + + PlayerCharacter source = (PlayerCharacter) this.getOwner(); + PlayerCharacter target = PlayerCharacter.getFromCache(msg.getPlayerID()); + Dispatch dispatch; + + if (!canTrade(source, target)) { + ChatManager.chatSystemError(source, "Can't currently trade with target player"); + return false; + } + + // TODO uncomment this block after we determine when we + // setBankOpen(false) and setVaultOpen(false) + CharacterItemManager cim1 = source.getCharItemManager(); + CharacterItemManager cim2 = target.getCharItemManager(); + + if (cim1 == null) + return false; + + if (cim2 == null) + return false; + + dispatch = Dispatch.borrow(target, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + + } + + public synchronized boolean invalidTradeRequest(InvalidTradeRequestMsg msg) { + PlayerCharacter requester = PlayerCharacter.getFromCache(msg.getRequesterID()); + Dispatch dispatch; + + dispatch = Dispatch.borrow(requester, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + + } + + public synchronized boolean canTrade(PlayerCharacter playerA, PlayerCharacter playerB) { + + if (playerA == null || playerB == null) + return false; + + //make sure both are alive + if (!playerA.isAlive() || !playerB.isAlive()) + return false; + + //distance check + Vector3fImmutable aLoc = playerA.getLoc(); + Vector3fImmutable bLoc = playerB.getLoc(); + + if (aLoc.distanceSquared2D(bLoc) > sqr(MBServerStatics.TRADE_RANGE)) + return false; + + //visibility check + if (!playerA.canSee(playerB) || !playerB.canSee(playerA)) + return false; + + if (playerA.lastBuildingAccessed != 0) { + ManageCityAssetsMsg mca = new ManageCityAssetsMsg(); + mca.actionType = 4; + mca.setTargetType(Enum.GameObjectType.Building.ordinal()); + mca.setTargetID(playerA.lastBuildingAccessed); + Dispatch dispatch = Dispatch.borrow(playerA, mca); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + playerA.lastBuildingAccessed = 0; + } + + return true; + } + + public synchronized boolean acceptTradeRequest(AcceptTradeRequestMsg msg) { + + PlayerCharacter source = (PlayerCharacter) this.getOwner(); + PlayerCharacter target = PlayerCharacter.getFromCache(msg.getTargetID()); + + Dispatch dispatch; + + if (source == null || !source.isAlive()) + return false; + + if (target == null || !target.isAlive()) + return false; + + if (this.tradingWith != null) + return false; + + if (!canTrade(source, target)) + return false; + + // verify characterTarget is in range + if (source.getLoc().distanceSquared2D(target.getLoc()) > sqr(MBServerStatics.TRADE_RANGE)) + return false; + + // TODO uncomment this block after we determine when we + // setBankOpen(false) and setVaultOpen(false) + /* + * CharacterItemManager cim1 = source.getCharItemManager(); + * CharacterItemManager cim2 = characterTarget.getCharItemManager(); if (cim1 == + * null) return false; if (cim2 == null) return false; if (cim1.isBankOpen()) + * return false; if (cim2.isVaultOpen()) return false; + */ + ClientConnection sourceConn = source.getClientConnection(); + ClientConnection targetConn = target.getClientConnection(); + + if (sourceConn == null) + return false; + + if (targetConn == null) + return false; + + + CharacterItemManager toTradeWith = target.getCharItemManager(); + + if (toTradeWith == null) + return false; + + Account sourceAccount = source.getAccount(); + Account targetAccount = target.getAccount(); + + UpdateVaultMsg uvmSource = new UpdateVaultMsg(sourceAccount); + UpdateVaultMsg uvmTarget = new UpdateVaultMsg(targetAccount); + + dispatch = Dispatch.borrow(source, uvmSource); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + dispatch = Dispatch.borrow(target, uvmTarget); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + this.setVaultOpen(false); + toTradeWith.setVaultOpen(false); + this.setBankOpen(false); + toTradeWith.setBankOpen(false); + + OpenTradeWindowMsg otwm = new OpenTradeWindowMsg(msg.getUnknown01(), source, target); + + // Only start trade if both players aren't already trading with + // someone + + if (this.getTradingWith() != null || toTradeWith.getTradingWith() != null) + return false; + + this.initializeTrade(); + toTradeWith.initializeTrade(); + this.setTradingWith(targetConn); + toTradeWith.setTradingWith(sourceConn); + this.tradeID = msg.getUnknown01(); + toTradeWith.tradeID = msg.getUnknown01(); + + dispatch = Dispatch.borrow(source, otwm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + dispatch = Dispatch.borrow(target, otwm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + return true; + } + + public synchronized boolean addItemToTradeWindow(AddItemToTradeWindowMsg msg) { + PlayerCharacter source = (PlayerCharacter) this.getOwner(); + Dispatch dispatch; + + if (source == null || !source.isAlive()) + return false; + + + ClientConnection ccOther = this.getTradingWith(); + + if (ccOther == null) + return false; + + PlayerCharacter other = ccOther.getPlayerCharacter(); + + if (other == null || !other.isAlive()) + return false; + + CharacterItemManager tradingWith = other.getCharItemManager(); + + if (tradingWith == null) + return false; + + if (!canTrade(source, other)) + return false; + + Item i = Item.getFromCache(msg.getItemID()); + + if (i == null) + return false; + + if (!this.doesCharOwnThisItem(i.getObjectUUID())) + return false; + + //can't add item to trade window twice + if (this.tradingContains(i)) + return false; + + //dupe check + if (!i.validForInventory(source.getClientConnection(), source, this)) + return false; + + if (!tradingWith.hasRoomTrade(i.getItemBase().getWeight())) { + dispatch = Dispatch.borrow(source, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + return false; + } + + UpdateTradeWindowMsg utwm = new UpdateTradeWindowMsg(source, other); + + this.setTradeCommitted((byte) 0); + tradingWith.setTradeCommitted((byte) 0); + + this.addItemToTrade(i); + + dispatch = Dispatch.borrow(other, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + modifyCommitToTrade(); + + dispatch = Dispatch.borrow(other, utwm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + return true; + } + + public synchronized boolean addGoldToTradeWindow(AddGoldToTradeWindowMsg msg) { + + PlayerCharacter source = (PlayerCharacter) this.getOwner(); + Dispatch dispatch; + + if (source == null || !source.isAlive()) + return false; + + + ClientConnection ccOther = this.getTradingWith(); + + if (ccOther == null) + return false; + + PlayerCharacter other = ccOther.getPlayerCharacter(); + + if (other == null || !other.isAlive()) + return false; + + CharacterItemManager tradingWith = other.getCharItemManager(); + + if (tradingWith == null) + return false; + + UpdateTradeWindowMsg utwm = new UpdateTradeWindowMsg(other, source); + UpdateTradeWindowMsg utwmOther = new UpdateTradeWindowMsg(source, other); + + if (!canTrade(source, other)) + return false; + + this.setTradeCommitted((byte) 0); + tradingWith.setTradeCommitted((byte) 0); + + int amt = msg.getAmount(); + + if (amt <= 0) { + Logger.info(source.getFirstName() + " added negative gold to trade window. Dupe attempt FAILED!"); + return false; + } + + if (amt > MBServerStatics.PLAYER_GOLD_LIMIT) + return false; + + if (this.getGoldInventory().getNumOfItems() - amt < 0) + return false; + + this.addGoldToTrade(amt); + + // BONUS CODE BELOW: Thanks some unknown retard! + // sourceItemMan.updateInventory(sourceItemMan.getInventory(), true); + + UpdateGoldMsg ugm = new UpdateGoldMsg(source); + ugm.configure(); + + modifyCommitToTrade(); + + dispatch = Dispatch.borrow(source, utwm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + dispatch = Dispatch.borrow(source, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + dispatch = Dispatch.borrow(other, utwmOther); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + + } + + public synchronized boolean uncommitToTrade(UncommitToTradeMsg msg) { + + PlayerCharacter source = (PlayerCharacter) this.getOwner(); + + if (source == null || !source.isAlive()) + return false; + + CharacterItemManager sourceItemMan = source.getCharItemManager(); + + if (sourceItemMan == null) + return false; + + sourceItemMan.setTradeCommitted((byte) 0); + + ClientConnection ccOther = sourceItemMan.getTradingWith(); + + if (ccOther == null) + return false; + + PlayerCharacter other = ccOther.getPlayerCharacter(); + + if (other == null) + return false; + + if (!canTrade(source, other)) + return false; + + return modifyCommitToTrade(); + } + + public synchronized boolean commitToTrade(CommitToTradeMsg msg) { + + PlayerCharacter source = (PlayerCharacter) this.getOwner(); + + if (source == null || !source.isAlive()) + return false; + + + this.setTradeCommitted((byte) 1); + + ClientConnection ccOther = this.getTradingWith(); + + if (ccOther == null) + return false; + + PlayerCharacter other = ccOther.getPlayerCharacter(); + + if (other == null || !other.isAlive()) + return false; + + CharacterItemManager tradingWith = other.getCharItemManager(); + + if (tradingWith == null) + return false; + + if (!canTrade(source, other)) + return false; + + modifyCommitToTrade(); + + if (this.getTradeCommitted() == (byte) 1 && tradingWith.getTradeCommitted() == (byte) 1) { + int tradeID = this.tradeID; + CloseTradeWindowMsg ctwm1 = new CloseTradeWindowMsg(source, tradeID); + CloseTradeWindowMsg ctwm2 = new CloseTradeWindowMsg(other, tradeID); + this.commitTrade(); + this.closeTradeWindow(ctwm1, false); + other.getCharItemManager().closeTradeWindow(ctwm2, false); + } + return true; + } + + private synchronized boolean modifyCommitToTrade() { + CharacterItemManager man1 = this; + + if (this.getTradingWith() == null) + return false; + + if (this.getTradingWith().getPlayerCharacter() == null) + return false; + CharacterItemManager man2 = this.getTradingWith().getPlayerCharacter().getCharItemManager(); + Dispatch dispatch; + + if (man1 == null || man2 == null) + return false; + + ModifyCommitToTradeMsg modify = new ModifyCommitToTradeMsg(this.getOwner(), man2.getOwner(), man1.getTradeCommitted(), + man2.getTradeCommitted()); + + dispatch = Dispatch.borrow((PlayerCharacter) this.getOwner(), modify); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + dispatch = Dispatch.borrow((PlayerCharacter) man2.getOwner(), modify); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return true; + + } + + public synchronized boolean closeTradeWindow(CloseTradeWindowMsg msg, boolean sourceTrade) { + + + Dispatch dispatch; + + PlayerCharacter source = (PlayerCharacter) this.getOwner(); + if (source == null) + return false; + + CharacterItemManager sourceItemMan = source.getCharItemManager(); + + if (sourceItemMan == null) + return false; + + int tradeID = this.tradeID; + CloseTradeWindowMsg closeMsg = new CloseTradeWindowMsg(source, tradeID); + + dispatch = Dispatch.borrow(source, closeMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + if (!sourceTrade) { + sourceItemMan.endTrade(); + return true; + } + + ClientConnection cc2 = sourceItemMan.getTradingWith(); + + if (cc2 == null || cc2.getPlayerCharacter() == null) { + sourceItemMan.endTrade(); + return true; + } + + sourceItemMan.endTrade(); + + + cc2.getPlayerCharacter().getCharItemManager().closeTradeWindow(msg, false); + + + return true; + } + + public Item getGoldInventory() { + if (this.goldInventory == null) + loadGoldItems(); + return this.goldInventory; + } + + public Item getGoldBank() { + if (this.goldBank == null) + loadGoldItems(); + return this.goldBank; + } + + public Item getGoldVault() { + if (this.goldVault == null) + loadGoldItems(); + return this.goldVault; + } + + public void addEquipOrder(int slot) { + synchronized (this.equipOrder) { + Integer iSlot = slot; + if (this.equipOrder.contains(iSlot)) + this.equipOrder.remove(iSlot); + this.equipOrder.add(slot); + } + } + + public synchronized boolean doesCharOwnThisItem(int itemID) { + return this.itemIDtoType.containsKey(itemID); + } + + public synchronized boolean junk(Item i) { + return junk(i, true); + } + + public synchronized boolean recycle(Item i) { + if (i.getObjectType() == GameObjectType.Item) + return junk(i, false); + else { + if (this.removeItemFromInventory(i) == false) + return false; + ((MobLoot) i).recycle((NPC) this.absCharacter); + calculateInventoryWeight(); + return true; + } + } + + // The DeleteItemMsg takes care of updating inventory, so we don't want to do it separately + public synchronized boolean delete(Item i) { + return junk(i, false); + } + + //cleanup an item from CharacterItemManager if it doesn't belong here + public synchronized boolean cleanupDupe(Item i) { + if (i == null) + return false; + + if (i.getItemBase().getType().equals(ItemType.GOLD)) { + if (this.getGoldInventory() != null) { + if (i.getObjectUUID() == this.getGoldInventory().getObjectUUID()) + this.goldInventory = null; + } else if (this.getGoldBank() != null) { + if (i.getObjectUUID() == this.getGoldBank().getObjectUUID()) + this.goldBank = null; + } + return true; + } + + byte slot = i.getEquipSlot(); + + if (this.doesCharOwnThisItem(i.getObjectUUID()) == false) + return false; + + // remove it from other lists: + this.remItemFromLists(i, slot); + this.itemIDtoType.remove(i.getObjectUUID()); + + calculateWeights(); + return true; + } + + public synchronized boolean consume(Item i) { + i.decrementChargesRemaining(); + if (i.getChargesRemaining() > 0) + return true; + return junk(i, true); + } + + private synchronized boolean junk(Item i, boolean updateInventory) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) { + if (this.getGoldInventory().getObjectUUID() == i.getObjectUUID()) + if (DbManager.ItemQueries.UPDATE_GOLD(i, 0)) { + this.getGoldInventory().setNumOfItems(0); + if (updateInventory) + updateInventory(); + return true; + } else { + return false; + } + if (!(this.absCharacter.getObjectType().equals(GameObjectType.Mob))) + return false; + } + + byte slot = i.getEquipSlot(); + + if (this.doesCharOwnThisItem(i.getObjectUUID()) == false && this.absCharacter.getObjectType() != GameObjectType.Mob && (i.containerType != Enum.ItemContainerType.FORGE)) + return false; + + // remove it from other lists: + this.remItemFromLists(i, slot); + this.itemIDtoType.remove(i.getObjectUUID()); + + i.junk(); + + //Why are we adding junked items?! + + // if (i.getObjectType() != GameObjectType.MobLoot) + // CharacterItemManager.junkedItems.add(i); + + + calculateWeights(); + + if (updateInventory) + // Send the new inventory + //updateInventory(i, false); this line was causing entire inventory to disappear + updateInventory(this.getInventory(), true); + + return true; + } + + public synchronized boolean moveItemToInventory(Item i) { + + boolean fromEquip = false; + synchronized (this) { + byte slot = i.getEquipSlot(); + + //Skip if NOT in vault. + if (i.containerType != Enum.ItemContainerType.VAULT) + if (this.doesCharOwnThisItem(i.getObjectUUID()) == false) + return false; + + // Only valid from bank, equip and vault + if (!bankContains(i) && !equippedContains(i) && !vaultContains(i)) + return false; + + if (equippedContains(i)) { + fromEquip = true; + ItemBase ib = i.getItemBase(); + if (ib != null && ib.getType().equals(ItemType.GOLD)) + this.absCharacter.cancelOnUnEquip(); + } + + // check to see what type of AbstractCharacter subclass we have stored + if (this.absCharacter.getClass() == PlayerCharacter.class) { + if (!i.moveItemToInventory((PlayerCharacter) this.absCharacter)) + return false; + } else if (!i.moveItemToInventory((NPC) this.absCharacter)) + return false; + + // remove it from other lists: + this.remItemFromLists(i, slot); + + // add to Inventory + this.inventory.add(i); + i.addToCache(); + this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal()); + + calculateWeights(); + } + + //Apply bonuses if from equip + if (fromEquip && this.absCharacter != null) { + this.absCharacter.applyBonuses(); + if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + this.absCharacter.incVer(); + } + + return true; + } + + public synchronized boolean moveItemToBank(Item i) { + byte slot = i.getEquipSlot(); + + if (this.doesCharOwnThisItem(i.getObjectUUID()) == false) + return false; + + // Item must be in inventory to move to bank + if (!this.inventory.contains(i)) + return false; + + // check to see what type of AbstractCharacter subclass we have stored + if (this.absCharacter.getClass() == PlayerCharacter.class) { + if (!i.moveItemToBank((PlayerCharacter) this.absCharacter)) + return false; + } else if (!i.moveItemToBank((NPC) this.absCharacter)) + return false; + + // remove it from other lists: + this.remItemFromLists(i, slot); + + // add to Bank + this.bank.add(i); + i.addToCache(); + + calculateWeights(); + + return true; + } + + public synchronized boolean moveGoldToBank(Item from, int amt) { + if (from == null) + return false; + if (from.getNumOfItems() - amt < 0) + return false; + if (this.goldBank.getNumOfItems() + amt > MBServerStatics.BANK_GOLD_LIMIT) { + if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter) { + PlayerCharacter pc = (PlayerCharacter) this.absCharacter; + if (pc.getClientConnection() != null) + ErrorPopupMsg.sendErrorPopup(pc, 202); + return false; + } + } + + if (!DbManager.ItemQueries.MOVE_GOLD(from, this.getGoldBank(), amt)) + return false; + from.setNumOfItems(from.getNumOfItems() - amt); + this.goldBank.setNumOfItems(this.goldBank.getNumOfItems() + amt); + return true; + } + + public synchronized boolean moveGoldToVault(Item from, int amt) { + if (from == null) + return false; + if (from.getNumOfItems() - amt < 0) + return false; + if (!DbManager.ItemQueries.MOVE_GOLD(from, this.account.vaultGold, amt)) + return false; + from.setNumOfItems(from.getNumOfItems() - amt); + this.account.vaultGold.setNumOfItems(this.goldVault.getNumOfItems() + amt); + return true; + } + + public synchronized boolean moveGoldToInventory(Item from, int amt) { + if (from == null) + return false; + if (from.getNumOfItems() - amt < 0 || amt < 1) + return false; + + if (this.goldInventory.getNumOfItems() + amt > MBServerStatics.PLAYER_GOLD_LIMIT) { + if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter) { + PlayerCharacter pc = (PlayerCharacter) this.absCharacter; + if (pc.getClientConnection() != null) + ErrorPopupMsg.sendErrorPopup(pc, 202); + return false; + } + } + + if (from instanceof MobLoot) { + if (!DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(), + this.goldInventory.getNumOfItems() + amt)) + return false; + } else if (!DbManager.ItemQueries.MOVE_GOLD(from, this.goldInventory, amt)) + return false; + from.setNumOfItems(from.getNumOfItems() - amt); + this.goldInventory.setNumOfItems(this.goldInventory.getNumOfItems() + amt); + return true; + } + + //This is called by the addGold devCmd. + public synchronized boolean addGoldToInventory(int amt, boolean fromDevCmd) { + + if (this.absCharacter == null || (!(this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)))) + return false; + + if (this.getGoldInventory().getNumOfItems() + amt > MBServerStatics.PLAYER_GOLD_LIMIT) { + return false; + } + + if (this.getGoldInventory().getNumOfItems() + amt < 0) + return false; + + + boolean worked = DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(), this.goldInventory.getNumOfItems() + amt); + if (worked) { + //log this since it's technically a dupe. Only use on test server! + if (fromDevCmd) { + String logString = this.absCharacter.getName() + " added " + amt + " gold to their inventory"; + Logger.info(logString); + } + this.goldInventory.setNumOfItems(this.goldInventory.getNumOfItems() + amt); + } + return worked; + } + + //Used to trainsfer gold from one inventory to another, for steal, etc. + public boolean transferGoldToMyInventory(AbstractCharacter tar, int amount) { + if (tar == null) + return false; + + CharacterItemManager tarCim = tar.getCharItemManager(); + if (tarCim == null) + return false; + + if (this.getGoldInventory().getNumOfItems() + amount < 0) + return false; + + if (this.getGoldInventory().getNumOfItems() + amount > MBServerStatics.PLAYER_GOLD_LIMIT) + return false; + + if (tarCim.getGoldInventory().getNumOfItems() - amount < 0) + return false; + + if (tarCim.getGoldInventory().getNumOfItems() - amount > MBServerStatics.PLAYER_GOLD_LIMIT) + return false; + + synchronized (this) { + synchronized (tarCim) { + if (!tarCim.addGoldToInventory(0 - amount, false)) //remove gold from target + return false; + if (!addGoldToInventory(amount, false)) //add to this inventory + return false; + } + } + return true; + } + + public synchronized boolean moveItemToVault(Item i) { + byte slot = i.getEquipSlot(); + + // if (this.doesCharOwnThisItem(i.getObjectUUID()) == false) + // return false; + + // Item must be in inventory to move to vault + if (!this.inventory.contains(i)) + return false; + + // check to see what type of AbstractCharacter subclass we have stored + if (this.absCharacter.getClass() == PlayerCharacter.class) { + if (!i.moveItemToVault(this.account)) + return false; + } else + return false; // NPC's dont have vaults! + + // remove it from other lists: + this.remItemFromLists(i, slot); + + // add to Vault + i.addToCache(); + + calculateWeights(); + + return true; + } + + //Used for buying MobEquipment from NPC + //Handles the gold transfer aspect + + // This removes ingame item from inventory for loot. + private synchronized boolean removeItemFromInventory(Item i) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) { + if (i.getObjectUUID() != this.getGoldInventory().getObjectUUID()) + return false; + if (!DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) { + return false; + } + + } else { + if (this.doesCharOwnThisItem(i.getObjectUUID()) == false) + return false; + if (this.inventory.contains(i)) { + this.inventory.remove(i); + this.itemIDtoType.remove(i.getObjectUUID()); + return true; + } + } + // tell client we're removing item + updateInventory(i, false); + return false; + } + + // This adds item to inventory for loot. Validity checks already handled + public synchronized boolean addItemToInventory(Item i) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) + if (this.absCharacter.getObjectType() == GameObjectType.Mob) { + if (this.goldInventory == null) + loadGoldItems(); + this.goldInventory.setNumOfItems(this.goldInventory.getNumOfItems() + i.getNumOfItems()); + } else { + int amt = i.getNumOfItems(); + if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, this.goldInventory.getNumOfItems() + amt)) { + updateInventory(); + return true; + } + + return false; + } + + this.inventory.add(i); + this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal()); + + ItemBase ib = i.getItemBase(); + if (ib != null) + this.inventoryWeight += ib.getWeight(); + return true; + } + + //called for adding gold of a specified amount + public synchronized boolean addItemToInventory(Item i, int amount) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) + return DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(), this.goldInventory.getNumOfItems() + amount); + return false; + } + + public boolean equipItem(Item i, byte slot) { + + synchronized (this) { + byte curSlot = i.getEquipSlot(); // Should be 0 + + if (this.doesCharOwnThisItem(i.getObjectUUID()) == false && this.absCharacter.getObjectType() != GameObjectType.Mob) { + Logger.error("Doesnt own item"); + return false; + } + + // Item must be in inventory to equip + if (!this.inventory.contains(i) && this.absCharacter.getObjectType() != GameObjectType.Mob) + return false; + + // make sure player can equip item + if (i.getItemBase() == null) + return false; + if (!i.getItemBase().canEquip(slot, this, absCharacter, i) && this.absCharacter.getObjectType() != GameObjectType.Mob) + return false; + + // check to see if item is already there. + Item old = this.equipped.get((int) slot); + if (old != null) { + Logger.error("already equipped"); + return false; + } + + // check to see what type of AbstractCharacter subclass we have stored + if (this.absCharacter.getClass() == PlayerCharacter.class) { + if (!i.equipItem((PlayerCharacter) this.absCharacter, slot)) + return false; + } else if (this.absCharacter.getObjectType() == GameObjectType.Mob) { + if (!i.equipItem((Mob) this.absCharacter, slot)) { + Logger.error("Failed to set Equip"); + return false; + } + + } else if (!i.equipItem((NPC) this.absCharacter, slot)) + return false; + + // remove it from other lists: + this.remItemFromLists(i, slot); + + // add to Equipped + this.equipped.put((int) slot, i); + i.addToCache(); + + addEquipOrder(i.getEquipSlot()); + + //calculateWeights(); + } + + //Apply Bonuses and update player + if (this.absCharacter != null) { + this.absCharacter.applyBonuses(); + if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + this.absCharacter.incVer(); + } + + return true; + } + + public synchronized boolean buyFromNPC(Building vendorBuilding, int cost, int buildingDeposit) { + + Item gold = this.getGoldInventory(); + + if (cost <= 0 || (gold.getNumOfItems() - cost) < 0) + return false; + + + if (this.getOwner() != null && this.getOwner().getObjectType().equals(GameObjectType.PlayerCharacter)) { + if (this.goldTradingAmount > 0) { + ErrorPopupMsg.sendErrorPopup((PlayerCharacter) this.getOwner(), 195); + return false; + } + } + + // Create gold from screatch instead of building strongbox + // if the NPC is not slotted. + + if (vendorBuilding == null) { + + return this.modifyInventoryGold(-cost); + } + + + if (vendorBuilding.getStrongboxValue() + cost > vendorBuilding.getMaxGold()) { + + if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter) { + PlayerCharacter pc = (PlayerCharacter) this.absCharacter; + if (pc.getClientConnection() != null) + ErrorPopupMsg.sendErrorPopup(pc, 206); + } + + return false; + } + + + // Update strongbox and inventory gold + if (!this.modifyInventoryGold(-cost)) + return false; + + City buildingCity = vendorBuilding.getCity(); + + if (buildingCity != null) { + buildingCity.transactionLock.writeLock().lock(); + try { + if (!vendorBuilding.transferGold(buildingDeposit, true)) + return false; + } catch (Exception e) { + Logger.error(e); + return false; + } finally { + buildingCity.transactionLock.writeLock().unlock(); + } + } else if (!vendorBuilding.transferGold(buildingDeposit, true)) + return false; + + return true; + } + + //Used for selling items to NPC + public synchronized boolean sellToNPC(Building building, int cost, Item item) { + + // Create gold from screatch instead of building strongbox + // if the NPC is not slotted. + + if (this.getGoldInventory().getNumOfItems() + cost < 0) + return false; + + if (this.getGoldInventory().getNumOfItems() + cost > MBServerStatics.PLAYER_GOLD_LIMIT) + return false; + + + if (this.getOwner().getCharItemManager().getGoldTrading() > 0) { + if (this.getOwner().getObjectType().equals(GameObjectType.PlayerCharacter)) + ErrorPopupMsg.sendErrorPopup((PlayerCharacter) this.getOwner(), 195); + return false; + } + + + if (building == null) { + return this.modifyInventoryGold(cost); + } + + //make sure strongbox can afford gold. + + if (!building.hasFunds(cost)) + return false; + + if ((building.getStrongboxValue() - cost) < 0) + return false; + + // Update strongbox and inventory gold + + if (!building.transferGold(-cost, false)) + return false; + + return this.modifyInventoryGold(cost); + } + + /** + * This sells an item to an npc + * + * @return True on success + */ + public synchronized boolean sellToNPC(Item itemToSell, NPC npc) { + + CharacterItemManager itemMan; + + if (itemToSell == null || npc == null) + return false; + + itemMan = npc.getCharItemManager(); + + if (itemMan == null) + return false; + + //test npc inventory is not full + + synchronized (this) { + synchronized (itemMan) { + if (!this.doesCharOwnThisItem(itemToSell.getObjectUUID())) + return false; + // attempt to transfer item in db + + boolean sdrMerchant = false; + + if (npc.getContractID() >= 1900 && npc.getContractID() <= 1906) + sdrMerchant = true; + + if (sdrMerchant) { + this.delete(itemToSell); + this.updateInventory(); + + } else if (!itemToSell.moveItemToInventory(npc)) + return false; + + // db transfer successfull, remove from this character + // skip this check if this is a mobLoot item (which is not in any inventory) + if (!sdrMerchant) + if (!removeItemFromInventory(itemToSell)) + return false; + + // add item to looter. + if (!sdrMerchant) + if (!itemMan.addItemToInventory(itemToSell)) + return false; + } + } + + // calculate new weights + calculateInventoryWeight(); + itemMan.calculateInventoryWeight(); + return true; + } + + /** + * This buys an item from an npc + * Handles transfer of item. + * + * @return True on success + */ + public synchronized boolean buyFromNPC(Item purchasedItem, NPC npc) { + + CharacterItemManager itemMan; + ItemBase itemBase; + + if (purchasedItem == null || npc == null) + return false; + + itemMan = npc.getCharItemManager(); + + if (itemMan == null) + return false; + + + synchronized (this) { + synchronized (itemMan) { + itemBase = purchasedItem.getItemBase(); + + if (itemBase == null) + return false; + + //test inventory is not full + + if (!hasRoomInventory(itemBase.getWeight())) + return false; + + if (!itemMan.inventory.contains(purchasedItem)) + return false; + // attempt to transfer item in db + + if (purchasedItem.getObjectType() == GameObjectType.MobLoot) { + + Item newItem = ((MobLoot) purchasedItem).promoteToItem((PlayerCharacter) this.absCharacter); + if (newItem == null) + return false; + + if (!itemMan.removeItemFromInventory(purchasedItem)) + return false; + + if (!addItemToInventory(newItem)) + return false; + //Item was created and still a mobloot item, remove from npc production list in db. + + DbManager.NPCQueries.REMOVE_FROM_PRODUCTION_LIST(purchasedItem.getObjectUUID(), npc.getObjectUUID()); + + + } else { + if (!purchasedItem.moveItemToInventory((PlayerCharacter) this.absCharacter)) + return false; + + if (purchasedItem.getValue() != purchasedItem.getMagicValue()) { + DbManager.ItemQueries.UPDATE_VALUE(purchasedItem, 0); + purchasedItem.setValue(0); + } + + // db transfer successfull, remove from this character + // skip this check if this is a mobLoot item (which is not in any inventory) + if (!itemMan.removeItemFromInventory(purchasedItem)) + return false; + + // add item to looter. + + if (!addItemToInventory(purchasedItem)) + return false; + } + + } + } + + // calculate new weights + calculateInventoryWeight(); + itemMan.calculateInventoryWeight(); + return true; + } + + /** + * Loot an item from an AbstractCharacter. Call this function on + * the CharacterItemManager of the current item owner, not the looter. + * This method will verify that the looter can receive the item + * (e.g. inventory isn't full). + * + * @param i Item being looted + * @param looter Player looting the item + * @param origin ClientConnection + * @return True on success + */ + public synchronized Item lootItemFromMe(Item i, PlayerCharacter looter, ClientConnection origin) { + return lootItemFromMe(i, looter, origin, false, -1); + } + + //This function is used for both looting and stealing + public synchronized Item lootItemFromMe(Item lootItem, PlayerCharacter lootingPlayer, ClientConnection origin, boolean fromSteal, int amount) { + + //TODO this function should have more logging + // make sure lootingPlayer exists + if (lootingPlayer == null) + return null; + + // get looters item manager + CharacterItemManager looterItems = lootingPlayer.getCharItemManager(); + + if (looterItems == null) + return null; + + if (fromSteal) { + if (!this.absCharacter.isAlive()) + return null; + } else if (!this.absCharacter.canBeLooted()) + return null; + + MobLoot mobLoot = null; + if (lootItem instanceof MobLoot) { + mobLoot = (MobLoot) lootItem; + if (mobLoot.isDeleted()) + return null; + } + + //Lock both ItemManagers; lower ID first + CharacterItemManager lockFirst; + CharacterItemManager lockSecond; + if (this.absCharacter.getObjectUUID() + < looterItems.absCharacter.getObjectUUID()) { + lockFirst = this; + lockSecond = looterItems; + } else { + lockFirst = looterItems; + lockSecond = this; + } + + synchronized (lockFirst) { + synchronized (lockSecond) { + // make sure current player has item in inventory + if (lootItem.getItemBase().getType().equals(ItemType.GOLD) && lootItem.getObjectUUID() != this.getGoldInventory().getObjectUUID() && !(this.absCharacter.getObjectType().equals(GameObjectType.Mob))) + return null; + else if (!this.inventory.contains(lootItem) && !this.getEquippedList().contains(lootItem) && !lootItem.getItemBase().getType().equals(ItemType.GOLD)) + return null; + + // get weight of item + ItemBase ib = lootItem.getItemBase(); + if (ib == null) + return null; + short weight = ib.getWeight(); + + // make sure lootingPlayer has room for item + if (!lootItem.getItemBase().getType().equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight)) + return null; + + if (lootItem.getItemBase().getType().equals(ItemType.GOLD)) + if (amount != -1) { //from steal + int total = lootItem.getNumOfItems(); + amount = (amount > total) ? total : amount; + if (!looterItems.moveGoldToInventory(lootItem, amount)) + return null; + if (mobLoot != null && amount == total) + this.delete(mobLoot); + } else { //from loot + if (!looterItems.moveGoldToInventory(lootItem, lootItem.getNumOfItems())) + return null; + if (mobLoot != null) // delete mobloot after it has been looted + this.delete(mobLoot); + } + else { //not Gold item + boolean created = false; + if (mobLoot != null) { + lootItem = mobLoot.promoteToItem(lootingPlayer); + + // delete mobloot after it has been looted + this.delete(mobLoot); + if (lootItem == null) + return null; + + created = true; + } + + // attempt to transfer item in db + + if (!lootItem.moveItemToInventory(lootingPlayer)) + return null; + + // db transfer successfull, remove from this character + // skip this check if this is a mobLoot item (which is not in any inventory) + if (mobLoot == null) + if (!removeItemFromInventory(lootItem)) + return null; + + // add item to lootingPlayer. + if (!looterItems.addItemToInventory(lootItem)) + return null; + } + } + } + + // calculate new weights + calculateInventoryWeight(); + looterItems.calculateInventoryWeight(); + + return lootItem; + } + + private synchronized void remItemFromLists(Item i, byte slot) { + + this.equipped.remove((int) slot); + this.vault.remove(i); + this.bank.remove(i); + this.inventory.remove(i); + } + + /* + * Delegates + */ + public synchronized boolean bankContains(Item i) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) + return (this.getGoldBank() != null && this.goldBank.getObjectUUID() == i.getObjectUUID()); + return bank.contains(i); + } + + public synchronized boolean inventoryContains(Item i) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) + return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == i.getObjectUUID()); + return inventory.contains(i); + } + + public synchronized boolean forgeContains(Item i, NPC vendor) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) + return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == i.getObjectUUID()); + return vendor.getRolling().contains(i); + } + + public synchronized boolean vaultContains(Item i) { + if (i.getItemBase().getType().equals(ItemType.GOLD)) + return (this.getGoldVault() != null && this.goldVault.getObjectUUID() == i.getObjectUUID()); + return this.account.getVault().contains(i); + } + + public synchronized boolean vaultContainsType(ItemBase ib) { + if (ib.getUUID() == 7) + return (this.getGoldVault() != null); + for (Item i : vault) { + if (i.getItemBase().getUUID() == ib.getUUID()) + return true; + } + return false; + } + + //for calling from devCmd fill vault. Already synchronized + public boolean vaultContainsTypeA(ItemBase ib) { + if (ib.getUUID() == 7) + return (this.getGoldVault() != null); + for (Item i : vault) { + if (i.getItemBase().getUUID() == ib.getUUID()) + return true; + } + return false; + } + + public synchronized boolean equippedContains(Item i) { + return equipped.containsValue(i); + } + + public synchronized Item getItemFromEquipped(int slot) { + return equipped.get(slot); + } + + public synchronized Item getItemByUUID(int objectUUID) { + if (this.itemIDtoType.containsKey(objectUUID)) { + + Integer integer = this.itemIDtoType.get(objectUUID); + if (integer == GameObjectType.Item.ordinal()) { + return Item.getFromCache(objectUUID); + } else if (integer == GameObjectType.MobLoot.ordinal()) { + return MobLoot.getFromCache(objectUUID); + } + + } + + if (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == objectUUID) + return this.goldInventory; + if (this.getGoldBank() != null && this.goldBank.getObjectUUID() == objectUUID) + return this.goldBank; + if (this.getGoldVault() != null && this.goldVault.getObjectUUID() == objectUUID) + return this.goldVault; + return null; + } + + public boolean tradingContains(Item i) { + if (this.trading == null || i == null) + return false; + return this.trading.contains(i.getObjectUUID()); + } + + public boolean isBankOpen() { + return this.bankOpened; + } + + public synchronized void setBankOpen(boolean bankOpened) { + this.bankOpened = bankOpened; + } + + public boolean isVaultOpen() { + return this.vaultOpened; + } + + public synchronized void setVaultOpen(boolean vaultOpened) { + this.vaultOpened = vaultOpened; + } + + public ClientConnection getTradingWith() { + return tradingWith; + } + + public synchronized void setTradingWith(ClientConnection tradingWith) { + this.tradingWith = tradingWith; + } + + public synchronized void clearTradingWith() { + this.tradingWith = null; + } + + public int getGoldTrading() { + return goldTradingAmount; + } + + public byte getTradeCommitted() { + return tradeCommitted; + } + + public synchronized void setTradeCommitted(byte tradeCommitted) { + this.tradeCommitted = tradeCommitted; + } + + public HashSet getTrading() { + return trading; + } + + /* + * List Copiers + */ + + public synchronized void addItemToTrade(Item i) { + this.trading.add(i.getObjectUUID()); + } + + public boolean getTradeSuccess() { + return tradeSuccess; + } + + public synchronized void setTradeSuccess(boolean tradeSuccess) { + this.tradeSuccess = tradeSuccess; + } + + public synchronized boolean RemoveEquipmentFromLackOfSkill(PlayerCharacter pc, boolean initialized) { + + + if (pc == null) + return false; + + if (this.equipped == null) + return false; + + + for (int slot : this.equipped.keySet()) { + + if (slot == MBServerStatics.SLOT_HAIRSTYLE || slot == MBServerStatics.SLOT_BEARDSTYLE) + continue; + + Item item = this.equipped.get(slot); + + if (item == null) { + this.equipped.remove(slot); + pc.applyBonuses(); + continue; + } + + if (!item.getItemBase().validForSkills(pc.getSkills())) { + this.forceToInventory(slot, item, pc, initialized); + pc.applyBonuses(); + } + } + + return true; + } + + /** + * Note that this method returns a copy of the internally stored + * list. + * + * @return the equipped + */ + public ConcurrentHashMap getEquipped() { + synchronized (this.equipped) { + return new ConcurrentHashMap<>(this.equipped); + } + } + + public ArrayList getEquippedList() { + ArrayList ret = new ArrayList<>(); + synchronized (this.equipOrder) { + synchronized (this.equipped) { + for (int slot : this.equipOrder) { + if (this.equipped.containsKey(slot)) + ret.add(this.equipped.get(slot)); + } + if (ret.size() != this.equipped.size()) + //missed adding some items, figure out what. + for (int slot : this.equipped.keySet()) { + if (!(this.equipOrder.contains(slot))) { + this.equipOrder.add(slot); + ret.add(this.equipped.get(slot)); + } + } + } + } + return ret; + } + + public Item getEquipped(int slot) { + synchronized (this.equipped) { + return this.equipped.get(slot); + } + } + + /** + * Note that this method returns a copy of the internally stored + * list. + * + * @return the inventory + */ + public ArrayList getInventory() { + return getInventory(false); + } + + public ArrayList getInventory(boolean sendGold) { + synchronized (this.inventory) { + ArrayList ret = new ArrayList<>(this.inventory); + if (sendGold && this.getGoldInventory() != null && this.goldInventory.getNumOfItems() > 0) + ret.add(this.goldInventory); + return ret; + } + } + + public int getInventoryCount() { + synchronized (this.inventory) { + return this.inventory.size(); + } + } + + /** + * Clears ownership of items. Called when player dies, but before + * respawning. + * + * @return the inventory + */ + public synchronized void orphanInventory() { + PlayerCharacter pc = null; + if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + pc = (PlayerCharacter) this.absCharacter; + synchronized (this.inventory) { + //dupe check, validate player properly owns all items + if (pc != null) { + Iterator iter = this.inventory.iterator(); + while (iter.hasNext()) { + Item item = iter.next(); + //this call may remove the item from this.inventory + if (!item.validForInventory(pc.getClientConnection(), pc, this)) { + } + } + } + + if (this.inventory.size() > 0) + DbManager.ItemQueries.ORPHAN_INVENTORY(this.inventory); + //make a copy of gold inventory for looting + //so we don't remove the goldInventory + if (this.getGoldInventory().getNumOfItems() > 0) { + int amt = this.goldInventory.getNumOfItems(); + if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) { + this.goldInventory.setNumOfItems(0); + MobLoot gold = new MobLoot(this.absCharacter, amt); + this.inventory.add(gold); + } + } + } + } + + /** + * This transfers the entire inventory to another list For populating + * corpse' inventory when player dies + * + * @return the inventory + */ + public synchronized void transferEntireInventory( + ArrayList newInventory, Corpse corpse, boolean enterWorld) { + + PlayerCharacter pc = null; + if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + pc = (PlayerCharacter) this.absCharacter; + + if (this.getGoldInventory().getNumOfItems() > 0) { + int amt = this.goldInventory.getNumOfItems(); + if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) { + this.goldInventory.setNumOfItems(0); + MobLoot gold = new MobLoot(this.absCharacter, amt); + newInventory.add(gold); + } + } + + for (Item item : this.inventory) { + if (item != null) + if (item instanceof MobLoot) { + + //MobLoot + item.zeroItem(); + item.containerType = Enum.ItemContainerType.INVENTORY; + + if (item.getItemBase().getType().equals(ItemType.GOLD)) + //only add gold item once + if (!corpse.hasGold()) + corpse.setHasGold(true); + newInventory.add(item); + } else //item + if (item.getItemBase().getType().equals(ItemType.GOLD)) { + int amt = item.getNumOfItems(); + item.setNumOfItems(0); + MobLoot ml = new MobLoot(this.absCharacter, amt); + ml.zeroItem(); + ml.containerType = Enum.ItemContainerType.INVENTORY; + if (!corpse.hasGold()) { + corpse.setHasGold(true); + newInventory.add(ml); + } + } else { + boolean transferred = item.moveItemToInventory(corpse); + if (!transferred) + Logger.error( + "CharItemManager.transferEntireInvetory", + "DB Error, Failed to transfer item " + + item.getObjectUUID() + " to new owner " + + corpse.getObjectUUID()); + newInventory.add(item); + + } + } + + // tell client we're clearing inventory + + + // clear the inventory. + this.inventory.clear(); + + //re-calculate inventory weight + calculateInventoryWeight(); + if (!enterWorld) + updateInventory(this.getInventory(), false); + } + + public synchronized void purgeInventory() { + + if (!this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) + return; + + if (this.goldInventory != null) + if (this.getGoldInventory().getNumOfItems() > 0) { + if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) { + this.goldInventory.setNumOfItems(0); + } + } + + if (this.inventory.size() > 0) + DbManager.ItemQueries.ORPHAN_INVENTORY(this.inventory); + + // clear the inventory. + this.inventory.clear(); + //re-calculate inventory weight + calculateInventoryWeight(); + } + + /** + * Note that this method returns a copy of the internally stored + * list. + * + * @return the bank + */ + public ArrayList getBank() { + synchronized (this.bank) { + ArrayList ret = new ArrayList<>(this.bank); + if (this.getGoldBank() != null && this.goldBank.getNumOfItems() > 0) + ret.add(this.goldBank); + return ret; + } + } + + /** + * Note that this method returns a copy of the internally stored + * list. + * + * @return the vault + */ + public ArrayList getVault() { + synchronized (this.vault) { + ArrayList ret = new ArrayList<>(this.vault); + if (this.getGoldVault() != null && this.goldVault.getNumOfItems() > 0) + ret.add(this.goldVault); + return ret; + } + } + + public boolean hasRoomInventory(short weight) { + if (this.absCharacter == null) + return false; + if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter) { + PlayerCharacter pc = (PlayerCharacter) this.absCharacter; + int newWeight = this.getCarriedWeight() + weight; + return newWeight <= (int) pc.statStrBase * 3; + } else if (this.absCharacter.getObjectType() == GameObjectType.NPC) { + int newWeight = this.getCarriedWeight() + weight; + return newWeight <= 1900 + (this.absCharacter.getLevel() * 3); + } else + return true; // npc's need checked + } + + public boolean hasRoomTrade(short itemWeight) { + + PlayerCharacter playerCharacter; + PlayerCharacter tradeCharacter; + + int tradeWeight; + + if (this.absCharacter == null) + return false; + + if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) == false) + return false; + + playerCharacter = (PlayerCharacter) this.absCharacter; + + if ((this.tradingWith == null) || + (this.tradingWith.isConnected() == false)) + return false; + + tradeCharacter = this.tradingWith.getPlayerCharacter(); + + tradeWeight = this.getCarriedWeight() + itemWeight; + tradeWeight = tradeWeight + tradeCharacter.getCharItemManager().getTradingWeight(); + tradeWeight = tradeWeight - this.getTradingWeight(); + + return tradeWeight <= (int) playerCharacter.statStrBase * 3; + } + + public boolean hasRoomBank(short weight) { + if (this.absCharacter == null) + return false; + return weight <= this.absCharacter.getBankCapacityRemaining(); + } + + public boolean hasRoomVault(short weight) { + if (this.absCharacter == null) + return false; + return weight <= this.absCharacter.getVaultCapacityRemaining(); + } + + public int getCarriedWeight() { + return getInventoryWeight() + getEquipWeight(); + } + + public int getInventoryWeight() { + return this.inventoryWeight; + } + + public int getBankWeight() { + return this.bankWeight; + } + + public int getEquipWeight() { + return this.equipWeight; + } + + public int getVaultWeight() { + return this.vaultWeight; + } + + public int getTradingForWeight() { + return calculateTradingForWeight(); + } + + public int getTradingWeight() { + + int weight = 0; + Item item; + + for (int i : this.trading) { + item = Item.getFromCache(i); + + if (item == null) + continue; + + ItemBase ib = item.getItemBase(); + weight += ib.getWeight(); + } + return weight; + } + + public AbstractCharacter getOwner() { + return this.absCharacter; + } + + public void calculateWeights() { + calculateBankWeight(); + calculateInventoryWeight(); + calculateEquipWeight(); + calculateVaultWeight(); + } + + public void calculateBankWeight() { + this.bankWeight = 0; + for (Item i : this.bank) { + ItemBase ib = i.getItemBase(); + if (ib != null) + this.bankWeight += ib.getWeight(); + } + } + + public void calculateEquipWeight() { + this.equipWeight = 0; + Collection c = this.equipped.values(); + Iterator it = c.iterator(); + while (it.hasNext()) { + Item i = it.next(); + ItemBase ib = i.getItemBase(); + if (ib != null) + this.equipWeight += ib.getWeight(); + } + } + + public void calculateInventoryWeight() { + this.inventoryWeight = 0; + for (Item i : this.inventory) { + ItemBase ib = i.getItemBase(); + if (ib != null) + this.inventoryWeight += ib.getWeight(); + } + } + + public void calculateVaultWeight() { + this.vaultWeight = 0; + for (Item i : this.vault) { + ItemBase ib = i.getItemBase(); + if (ib != null) + this.vaultWeight += ib.getWeight(); + } + } + + private int calculateTradingForWeight() { + int tradingForWeight = 0; + + return tradingForWeight; + } + + public void updateInventory(Item item, boolean add) { + ArrayList list = new ArrayList<>(); + list.add(item); + updateInventory(list, add); + } + + private void updateInventory(ArrayList inventory, boolean add) { + + if (this.absCharacter == null) + return; + + if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) == false) + return; + + PlayerCharacter pc = (PlayerCharacter) this.absCharacter; + + UpdateInventoryMsg updateInventoryMsg = new UpdateInventoryMsg(inventory, this.getBank(), this.getGoldInventory(), add); + Dispatch dispatch = Dispatch.borrow(pc, updateInventoryMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + public void forceToInventory(int slot, Item item, PlayerCharacter pc, boolean initialized) { + if (item == null || pc == null) + return; + + if (!item.moveItemToInventory(pc)) { + //TODO well why did this fail? clean it up + } + + // remove it from other lists: + this.remItemFromLists(item, (byte) slot); + + // add to Inventory + this.inventory.add(item); + item.addToCache(); + + calculateWeights(); + + //Update players with unequipped item + if (initialized) { + TransferItemFromEquipToInventoryMsg back = new TransferItemFromEquipToInventoryMsg(pc, slot); + DispatchMessage.dispatchMsgToInterestArea(pc, back, engine.Enum.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + + } + + /** + * Update the player's inventory window by resending the entire contents. + */ + public void updateInventory() { + this.updateInventory(this.getInventory(), true); + } + + public synchronized void initializeTrade() { + this.trading = new HashSet<>(); + } + + public synchronized boolean commitTrade() { + int goldFrom1 = 0; + int goldFrom2 = 0; + + if (this.getTradingWith() == null || this.getTradingWith().isConnected() == false + || this.getTradingWith().getPlayerCharacter() == null) { + this.endTrade(); + return false; + } + + + CharacterItemManager tradingWith = this.getTradingWith().getPlayerCharacter().getCharItemManager(); + + if (tradingWith == null) + return false; + + if (this.goldTradingAmount != 0) { + + if (tradingWith.goldInventory == null) { + Logger.error("Null Gold for player " + this.getOwner().getObjectUUID()); + return false; + } + goldFrom1 = this.goldTradingAmount; + } + if (tradingWith.goldTradingAmount != 0) { + + if (this.getGoldInventory() == null) { + Logger.error("Null Gold for player " + this.getOwner().getObjectUUID()); + return false; + } + goldFrom2 = tradingWith.goldTradingAmount; + } + + + if (this.getGoldInventory().getNumOfItems() + goldFrom2 > 10000000) { + PlayerCharacter pc = (PlayerCharacter) this.absCharacter; + if (pc.getClientConnection() != null) + ErrorPopupMsg.sendErrorPopup(pc, 202); + return false; + } + + + if (tradingWith.getGoldInventory().getNumOfItems() + goldFrom1 > 10000000) { + PlayerCharacter pc = (PlayerCharacter) tradingWith.absCharacter; + if (pc.getClientConnection() != null) + ErrorPopupMsg.sendErrorPopup(pc, 202); + return false; + } + + if (this.trading.size() > 0 || tradingWith.trading.size() > 0 || goldFrom1 > 0 || goldFrom2 > 0) { + if (!DbManager.ItemQueries.DO_TRADE(this.trading, tradingWith.trading, this, tradingWith, + this.goldInventory, tradingWith.goldInventory, goldFrom1, goldFrom2)) + return false; + } else + return true; + + for (int i : this.trading) { + Item item = Item.getFromCache(i); + if (item == null) + continue; + this.trade(item); + tradingWith.tradeForItem(item); + } + for (int i : tradingWith.trading) { + Item item = Item.getFromCache(i); + if (item == null) + continue; + tradingWith.trade(item); + this.tradeForItem(item); + } + + //subtract gold your trading from your inventory. + if (this.goldTradingAmount > 0) + this.getGoldInventory().setNumOfItems(this.getGoldInventory().getNumOfItems() - this.goldTradingAmount); + //subtract gold your trading from your inventory. + if (tradingWith.goldTradingAmount > 0) + tradingWith.getGoldInventory().setNumOfItems(tradingWith.getGoldInventory().getNumOfItems() - tradingWith.goldTradingAmount); + + if (tradingWith.goldTradingAmount > 0) + this.getGoldInventory().setNumOfItems(this.goldInventory.getNumOfItems() + + tradingWith.goldTradingAmount); + if (this.goldTradingAmount > 0) + tradingWith.getGoldInventory().setNumOfItems(tradingWith.goldInventory.getNumOfItems() + + this.goldTradingAmount); + + this.tradeSuccess = true; + tradingWith.tradeSuccess = true; + + return true; + + } + + public synchronized void endTrade() { + updateInventory(this.getInventory(), true); + this.tradeCommitted = (byte) 0; + this.tradeSuccess = false; + this.tradingWith = null; + this.trading = null; + this.goldTradingAmount = 0; + this.tradeID = 0; + } + + public synchronized void endTrade(boolean fromDeath) { + this.tradeCommitted = (byte) 0; + this.tradeSuccess = false; + this.tradingWith = null; + this.trading = null; + this.goldTradingAmount = 0; + } + + // Remove item from your possession + private synchronized boolean trade(Item i) { + if (this.doesCharOwnThisItem(i.getObjectUUID()) == false) + return false; + + // Only valid from inventory + if (!inventoryContains(i)) + return false; + + // remove from Inventory + this.inventory.remove(i); + this.itemIDtoType.remove(i.getObjectUUID()); + i.setOwnerID(0); + + calculateWeights(); + + return true; + } + + //Damage an equipped item a specified amount + public void damageItem(Item item, int amount) { + if (item == null || amount < 1 || amount > 5) + return; + + //verify the item is equipped by this player + int slot = item.getEquipSlot(); + if (!this.equipped.containsKey(slot)) + return; + Item verify = this.equipped.get(slot); + if (verify == null || item.getObjectUUID() != verify.getObjectUUID()) + return; + + //don't damage noob gear, hair or beards. + if (item.getDurabilityMax() == 0) + return; + + if (!item.isCanDestroy()) + return; + + int dur = (int) item.getDurabilityCurrent(); + if (dur - amount <= 0) { + //destroy the item + junk(item); + + //TODO remove item from the client + //This may not be correct + dur = 0; + } else { + dur -= amount; + if (!DbManager.ItemQueries.SET_DURABILITY(item, dur)) + return; + item.setDurabilityCurrent((short) dur); + + } + + if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) == false) + return; + + //send damage item msg to client + PlayerCharacter pc = (PlayerCharacter) this.absCharacter; + + ItemHealthUpdateMsg itemHealthUpdateMsg = new ItemHealthUpdateMsg(slot, (float) dur); + Dispatch dispatch = Dispatch.borrow(pc, itemHealthUpdateMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + //Damage a random piece of armor a specified amount + public void damageRandomArmor(int amount) { + ArrayList armor = new ArrayList<>(); + if (this.equipped.containsKey(MBServerStatics.SLOT_OFFHAND)) { + Item item = this.equipped.get(MBServerStatics.SLOT_OFFHAND); + ItemBase ib = item.getItemBase(); + if (ib.isShield()) + armor.add(item); + } + if (this.equipped.containsKey(MBServerStatics.SLOT_HELMET)) + armor.add(this.equipped.get(MBServerStatics.SLOT_HELMET)); + if (this.equipped.containsKey(MBServerStatics.SLOT_CHEST)) + armor.add(this.equipped.get(MBServerStatics.SLOT_CHEST)); + if (this.equipped.containsKey(MBServerStatics.SLOT_ARMS)) + armor.add(this.equipped.get(MBServerStatics.SLOT_ARMS)); + if (this.equipped.containsKey(MBServerStatics.SLOT_GLOVES)) + armor.add(this.equipped.get(MBServerStatics.SLOT_GLOVES)); + if (this.equipped.containsKey(MBServerStatics.SLOT_GLOVES)) + armor.add(this.equipped.get(MBServerStatics.SLOT_GLOVES)); + if (this.equipped.containsKey(MBServerStatics.SLOT_LEGGINGS)) + armor.add(this.equipped.get(MBServerStatics.SLOT_LEGGINGS)); + if (this.equipped.containsKey(MBServerStatics.SLOT_FEET)) + armor.add(this.equipped.get(MBServerStatics.SLOT_FEET)); + + if (armor.isEmpty()) + return; //nothing to damage + + int roll = ThreadLocalRandom.current().nextInt(armor.size()); + damageItem(armor.get(roll), amount); + } + + //Damage all equipped gear a random amount between 1 and 5 + public void damageAllGear() { + for (Item gear : this.equipped.values()) { + damageItem(gear, (ThreadLocalRandom.current().nextInt(5) + 1)); + } + } + + // Add item to your possession + public synchronized boolean tradeForItem(Item i) { + // add to Inventory + this.inventory.add(i); + this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal()); + i.setOwnerID(this.absCharacter.getObjectUUID()); + + calculateWeights(); + + return true; + } + + public synchronized boolean addGoldToTrade(int amount) { + + if (this.goldTradingAmount + amount > MBServerStatics.PLAYER_GOLD_LIMIT) + return false; + + this.goldTradingAmount += amount; + + return true; + } + + /** + * Completely empties inventory, deleting any items. Use with caution! + */ + public synchronized void clearInventory() { + this.getGoldInventory().setNumOfItems(0); + Iterator ii = this.inventory.iterator(); + while (ii.hasNext()) { + Item itm = ii.next(); + ii.remove(); + this.delete(itm); + } + } + + public synchronized void clearEquip() { + + ArrayList equipCopy = new ArrayList<>(this.getEquippedList()); + Iterator ii = equipCopy.iterator(); + while (ii.hasNext()) { + Item itm = ii.next(); + this.getEquippedList().remove(itm); + this.delete(itm); + } + } + + public byte getEquipVer() { + return this.equipVer; + } + + public void incEquipVer() { + this.equipVer++; + } + + public void incInventoryVer() { + this.equipVer++; + } + + public void incBankVer() { + this.equipVer++; + } + + public void incVaultVer() { + this.equipVer++; + } + + public int getTradeID() { + return tradeID; + } + + public synchronized boolean closeTradeWindow() { + if (this.getTradingWith() != null || this.getTradeID() != 0) + this.closeTradeWindow(new CloseTradeWindowMsg(this.getOwner(), this.getTradeID()), true); + return true; + + } + +} diff --git a/src/engine/objects/CharacterPower.java b/src/engine/objects/CharacterPower.java new file mode 100644 index 00000000..c220438f --- /dev/null +++ b/src/engine/objects/CharacterPower.java @@ -0,0 +1,623 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.net.ByteBufferWriter; +import engine.net.client.msg.ErrorPopupMsg; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + + +public class CharacterPower extends AbstractGameObject { + + private final PowersBase power; + private AtomicInteger trains = new AtomicInteger(); + private short grantedTrains; + private int ownerUID; + private boolean trained = false; + private int requiredLevel = 0; + + + /** + * No Table ID Constructor + */ + public CharacterPower(PowersBase power, PlayerCharacter pc) { + super(); + this.power = power; + this.trains.set(0); + this.grantedTrains = this.grantedTrains; + this.ownerUID = pc.getObjectUUID(); + + } + + /** + * Normal Constructor + */ + public CharacterPower(PowersBase power, PlayerCharacter pc, int newUUID) { + super(newUUID); + this.power = power; + this.trains.set(0); + this.grantedTrains = this.grantedTrains; + this.ownerUID = pc.getObjectUUID(); + + + } + + + /** + * ResultSet Constructor + */ + public CharacterPower(ResultSet rs, PlayerCharacter pc) throws SQLException { + super(rs); + int powersBaseToken = rs.getInt("PowersBaseToken"); + this.power = PowersManager.getPowerByToken(powersBaseToken); + + if (this.power != null && this.power.isWeaponPower()) + this.trains.set(0); + else + this.trains.set(rs.getInt("trains")); + this.grantedTrains = this.grantedTrains; + this.ownerUID = pc.getObjectUUID(); + + } + + public CharacterPower(ResultSet rs) throws SQLException { + super(rs); + int powersBaseToken = rs.getInt("PowersBaseToken"); + this.power = PowersManager.getPowerByToken(powersBaseToken); + this.trains.set(rs.getInt("trains")); + this.grantedTrains = this.grantedTrains; + this.ownerUID = rs.getInt("CharacterID"); + + // this.owner = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(rs.getInt("CharacterID")); + } + + public static PlayerCharacter getOwner(CharacterPower cp) { + return PlayerCharacter.getFromCache(cp.ownerUID); + } + + /* + * This iterates through players runes and adds and removes powers as needed + * Don't Call this directly. Instead call pc.calculateSkills(). + */ + public static void calculatePowers(PlayerCharacter pc) { + if (pc == null) + return; + + // First add powers that don't exist + ConcurrentHashMap powers = pc.getPowers(); + // ArrayList genericPowers = PowerReq.getPowerReqsForAll(); + // CharacterPower.grantPowers(genericPowers, powers, pc); + Race race = pc.getRace(); + if (race != null) { + CharacterPower.grantPowers(race.getPowersGranted(), powers, pc); + } else + Logger.error("Failed to find Race for player " + pc.getObjectUUID()); + BaseClass bc = pc.getBaseClass(); + if (bc != null) { + CharacterPower.grantPowers(bc.getPowersGranted(), powers, pc); + } else + Logger.error("Failed to find BaseClass for player " + pc.getObjectUUID()); + PromotionClass promo = pc.getPromotionClass(); + if (promo != null) + CharacterPower.grantPowers(promo.getPowersGranted(), powers, pc); + ArrayList runes = pc.getRunes(); + if (runes != null) { + for (CharacterRune rune : runes) { + CharacterPower.grantPowers(rune.getPowersGranted(), powers, pc); + } + } else + Logger.error("Failed to find Runes list for player " + pc.getObjectUUID()); + + // next remove any skills that no longer belong + Iterator it = powers.keySet().iterator(); + while (it.hasNext()) { + Integer token = it.next(); + boolean valid = false; + // if (CharacterPower.powerAllowed(token, genericPowers, pc)) + // continue; + if (CharacterPower.powerAllowed(token, race.getPowersGranted(), pc)) + continue; + if (CharacterPower.powerAllowed(token, bc.getPowersGranted(), pc)) + continue; + if (promo != null) + if (CharacterPower.powerAllowed(token, promo.getPowersGranted(), pc)) + continue; + for (CharacterRune rune : runes) { + if (CharacterPower.powerAllowed(token, rune.getPowersGranted(), pc)) { + valid = true; + continue; + } + } + + // if power doesn't belong to any runes or skill, then remove it + if (!valid) { + CharacterPower cp = powers.get(token); + DbManager.CharacterPowerQueries.DELETE_CHARACTER_POWER(cp.getObjectUUID()); + it.remove(); + } + } + } + + /* + * This grants powers for specific runes + */ + private static void grantPowers(ArrayList powersGranted, ConcurrentHashMap powers, PlayerCharacter pc) { + ConcurrentHashMap skills = pc.getSkills(); + + for (PowerReq powerreq : powersGranted) { + PowersBase powersBase = powerreq.getPowersBase(); + + if (powersBase == null) + continue; + // skip if player already has power + if (powers.containsKey(powerreq.getToken())) { + CharacterPower cp = powers.get(powersBase.getToken()); + if (cp != null) + if (cp.requiredLevel == 0) { + cp.requiredLevel = (int) powerreq.getLevel(); + } + + continue; + } + + // If player not high enough level for power, then skip + if (pc.getLevel() < powerreq.getLevel()) + continue; + + // See if any prereq powers needed + boolean valid = true; + ConcurrentHashMap preqs = powerreq.getPowerReqs(); + for (Integer tok : preqs.keySet()) { + if (!powers.containsKey(tok)) + valid = false; + else { + CharacterPower cpp = powers.get(tok); + if ((cpp.getTrains() + cpp.grantedTrains) < preqs.get(tok)) + valid = false; + } + } + if (!valid) + continue; + + // See if any prereq skills needed + preqs = powerreq.getSkillReqs(); + for (Integer tok : preqs.keySet()) { + if (tok == 0) + continue; + CharacterSkill found = null; + for (CharacterSkill sk : skills.values()) { + if (sk.getToken() == tok) { + found = sk; + continue; + } + } + if (found != null) { + if (found.getModifiedAmountBeforeMods() < preqs.get(tok)) + valid = false; + } else + valid = false; + } + if (!valid) + continue; + + + if (!powers.containsKey(powersBase.getToken())) { + CharacterPower newPower = new CharacterPower(powersBase, pc); + CharacterPower cp = null; + try { + cp = DbManager.CharacterPowerQueries.ADD_CHARACTER_POWER(newPower); + } catch (Exception e) { + cp = null; + } + if (cp != null) { + cp.requiredLevel = (int) powerreq.getLevel(); + powers.put(powersBase.getToken(), cp); + } else + Logger.error("Failed to add CharacterPower to player " + pc.getObjectUUID()); + } else { + CharacterPower cp = powers.get(powersBase.getToken()); + if (cp != null) + if (cp.requiredLevel == 0) { + cp.requiredLevel = (int) powerreq.getLevel(); + } + } + } + } + + public static void grantTrains(PlayerCharacter pc) { + if (pc == null) + return; + ConcurrentHashMap powers = pc.getPowers(); + for (CharacterPower cp : powers.values()) { + cp.grantedTrains = cp.getGrantedTrains(pc); + } + } + + /* + * This verifies if a power is valid for a players rune + */ + private static boolean powerAllowed(Integer token, ArrayList powersGranted, PlayerCharacter pc) { + ConcurrentHashMap skills = pc.getSkills(); + ConcurrentHashMap powers = pc.getPowers(); + if (skills == null || powers == null) + return false; + for (PowerReq powerreq : powersGranted) { + PowersBase pb = powerreq.getPowersBase(); + if (pb != null) { + if (pb.getToken() == token) { + + //test level requirements + if (powerreq.getLevel() > pc.getLevel()) { + return false; + } + + //test skill requirements are met + ConcurrentHashMap skillReqs = powerreq.getSkillReqs(); + for (int tok : skillReqs.keySet()) { + boolean valid = false; + if (tok == 0) + continue; + for (CharacterSkill skill : skills.values()) { + if (skill.getToken() == tok) { + if (skill.getModifiedAmountBeforeMods() < skillReqs.get(tok)) + return false; + valid = true; + break; + } + } + if (!valid) + return false; + } + + //test power prerequisites are met + ConcurrentHashMap powerReqs = powerreq.getPowerReqs(); + for (int tok : powerReqs.keySet()) { + if (!powers.containsKey(tok)) + return false; + CharacterPower cp = powers.get(tok); + if (cp.getTotalTrains() < powerReqs.get(tok)) + return false; + } + + //everything passed. power is valid + return true; + } + } + } + return false; + } + + public static void serializeForClientMsg(CharacterPower characterPower, ByteBufferWriter writer) { + if (characterPower.power != null) + writer.putInt(characterPower.power.getToken()); + else + writer.putInt(0); + writer.putInt(characterPower.getTrains()); + } + + public static CharacterPower getPower(int tableId) { + return DbManager.CharacterPowerQueries.GET_CHARACTER_POWER(tableId); + } + + private short getGrantedTrains(PlayerCharacter pc) { + if (this.power != null && pc != null) { + // if (this.power.isWeaponPower()) { + // SkillsBase sb = null; + // try { + // sb = SkillsBase.getSkillsBaseByName(this.power.getSkillName()); + // } catch (SQLException e) {} + // if (sb != null) { + // return pc.getBonuses().getByte("gt." + sb.getToken()); + // } else + // return pc.getBonuses().getByte("gt." + this.power.getToken()); + // } else + // return pc.getBonuses().getByte("gt." + this.power.getToken()); + return PowerGrant.getGrantedTrains(this.power.getToken(), pc); + } else + return 0; + } + + /* + * Getters + */ + public PowersBase getPower() { + return power; + } + + public int getPowerID() { + return power.getUUID(); + } + + public boolean isTrained() { + return trained; + } + + public void setTrained(boolean b) { + trained = b; + } + + public int getTrains() { + return this.trains.get(); + } + + + /* + * Utils + */ + + public short getGrantedTrains() { + return this.grantedTrains; + } + + public int getTotalTrains() { + return (this.trains.get() + this.grantedTrains); + } + + public float getTrainingCost(PlayerCharacter pc, NPC trainer) { + int charLevel = pc.getLevel(); + int skillRank = this.trains.get() - 1 + this.requiredLevel; + + + float baseCost = 50 * this.requiredLevel; //TODO GET BASE COSTS OF SKILLS. + + + float sellPercent = -4f; //NOT SELL PERCENT! + float cost; + float const5; + int const2 = 1; + float const3 = 50; + float const4 = const3 + const2; + if (charLevel > 50) + const5 = 50 / const4; + else + const5 = charLevel / const4; + + const5 = 1 - const5; + const5 = (float) (Math.log(const5) / Math.log(2) * .75f); + float rounded5 = Math.round(const5); + const5 = rounded5 - const5; + + const5 *= -1; + + const5 = (float) (Math.pow(2, const5) - 1); + + const5 += 1; + const5 = Math.scalb(const5, (int) rounded5); + const5 *= (charLevel - skillRank); + const5 *= sellPercent; + + const5 = (float) (Math.log(const5) / Math.log(2) * 3); + rounded5 = Math.round(const5); + const5 = rounded5 - const5; + const5 *= -1; + const5 = (float) (Math.pow(2, const5) - 1); + const5 += 1; + + + const5 = Math.scalb(const5, (int) rounded5); + const5 += 1; + cost = const5 * baseCost; + + + if (Float.isNaN(cost)) + cost = baseCost; + return cost; + } + + public synchronized boolean train(PlayerCharacter pc) { + if (pc == null || this.power == null) + return false; + + //see if any prereqs to train this power is met + if (!canTrain(pc)) + return false; + + boolean succeeded = true; + int oldTrains = this.trains.get(); + int tr = oldTrains + this.grantedTrains; + if (pc.getTrainsAvailable() <= 0) + return false; + if (tr == this.power.getMaxTrains()) //at max, stop here + return false; + else if (tr > this.power.getMaxTrains()) //catch incase we somehow go over + this.trains.set((this.power.getMaxTrains() - this.grantedTrains)); + else //add the train + succeeded = this.trains.compareAndSet(oldTrains, oldTrains + 1); + + if (this.trains.get() > this.power.getMaxTrains()) { //double check not over max trains + this.trains.set(this.power.getMaxTrains()); + succeeded = false; + } + + if (succeeded) { + this.trained = true; + + //update database + pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS); + + //subtract from trains available + pc.modifyTrainsAvailable(-1); + + pc.calculateSkills(); + return true; + } else + return false; + } + + public boolean reset(PlayerCharacter pc) { + if (pc == null || this.power == null) + return false; + + //see if any prereqs to refine this power is met + + boolean succeeded = true; + int oldTrains = this.trains.get(); + int tr = oldTrains + this.grantedTrains; + if (oldTrains < 1) + return false; + else //subtract the train + succeeded = this.trains.compareAndSet(oldTrains, 0); + if (succeeded) { + this.trained = true; + + //update database + pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS); + + //subtract from trains available + pc.modifyTrainsAvailable(oldTrains); + + pc.calculateSkills(); + return true; + } else + return false; + } + + public boolean refine(PlayerCharacter pc) { + if (pc == null || this.power == null) + return false; + + //see if any prereqs to refine this power is met + if (!canRefine(pc)) + return false; + + boolean succeeded = true; + int oldTrains = this.trains.get(); + int tr = oldTrains + this.grantedTrains; + if (oldTrains < 1) + return false; + else //subtract the train + succeeded = this.trains.compareAndSet(oldTrains, oldTrains - 1); + if (succeeded) { + this.trained = true; + + //update database + pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS); + + //subtract from trains available + pc.modifyTrainsAvailable(1); + + pc.calculateSkills(); + return true; + } else + return false; + } + + //This verifies the power is not blocked from refining by prereqs on other powers. + private boolean canRefine(PlayerCharacter pc) { + if (this.power == null || pc == null) + return false; + + ConcurrentHashMap powers = pc.getPowers(); + Race race = pc.getRace(); + if (race != null) { + if (!canRefine(race.getPowersGranted(), powers, pc)) + return false; + } else + return false; + BaseClass bc = pc.getBaseClass(); + if (bc != null) { + if (!canRefine(bc.getPowersGranted(), powers, pc)) + return false; + } else + return false; + PromotionClass promo = pc.getPromotionClass(); + if (promo != null) + if (!canRefine(promo.getPowersGranted(), powers, pc)) + return false; + ArrayList runes = pc.getRunes(); + if (runes != null) { + for (CharacterRune rune : runes) { + if (!canRefine(rune.getPowersGranted(), powers, pc)) + return false; + } + } + + //all tests passed. Can refine + return true; + } + + /* + * Serializing + */ + + private boolean canRefine(ArrayList powersGranted, ConcurrentHashMap powers, PlayerCharacter pc) { + for (PowerReq pr : powersGranted) { + ConcurrentHashMap powerReqs = pr.getPowerReqs(); + for (int token : powerReqs.keySet()) { + if (token == this.power.getToken()) { + //this is a prereq, find the power and make sure it has enough trains + int trainsReq = (int) powerReqs.get(token); + for (CharacterPower cp : powers.values()) { + if (cp.power.getToken() == pr.getToken()) { + if (this.getTotalTrains() <= trainsReq && cp.getTrains() > 0) { + ErrorPopupMsg.sendErrorMsg(pc, "You must refine " + cp.power.getName() + " to 0 before refining any more from this power."); + return false; + } + } + } + } + } + } + + return true; + } + + private boolean canTrain(PlayerCharacter pc) { + if (this.power == null || pc == null) + return false; + int token = this.power.getToken(); + boolean valid = false; + Race race = pc.getRace(); + if (race != null) { + if (CharacterPower.powerAllowed(token, race.getPowersGranted(), pc)) + return true; + } else + return false; + BaseClass bc = pc.getBaseClass(); + if (bc != null) { + if (CharacterPower.powerAllowed(token, bc.getPowersGranted(), pc)) + return true; + } else + return false; + PromotionClass promo = pc.getPromotionClass(); + if (promo != null) + if (CharacterPower.powerAllowed(token, promo.getPowersGranted(), pc)) + return true; + ArrayList runes = pc.getRunes(); + for (CharacterRune rune : runes) + if (CharacterPower.powerAllowed(token, rune.getPowersGranted(), pc)) + return true; + return false; + } + + @Override + public void updateDatabase() { + DbManager.CharacterPowerQueries.updateDatabase(this); + } + + public int getRequiredLevel() { + return requiredLevel; + } + + public void setRequiredLevel(int requiredLevel) { + this.requiredLevel = requiredLevel; + } +} diff --git a/src/engine/objects/CharacterRune.java b/src/engine/objects/CharacterRune.java new file mode 100644 index 00000000..c9e10e66 --- /dev/null +++ b/src/engine/objects/CharacterRune.java @@ -0,0 +1,211 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.DispatchChannel; +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; +import engine.net.DispatchMessage; +import engine.net.client.msg.ApplyRuneMsg; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class CharacterRune extends AbstractGameObject { + + private final RuneBase runeBase; + private final int player; + private final ArrayList skillsGranted; + private final ArrayList powersGranted; + private final ArrayList effectsGranted; + + /** + * No Table ID Constructor + */ + public CharacterRune(RuneBase runeBase, int characterID) { + super(); + this.runeBase = runeBase; + this.player = characterID; + if (this.runeBase != null) { + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); + this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID()); + } else { + this.skillsGranted = new ArrayList<>(); + this.powersGranted = new ArrayList<>(); + } + if (this.runeBase != null) + this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID()); + else + this.effectsGranted = new ArrayList<>(); + } + + /** + * Normal Constructor + */ + public CharacterRune(RuneBase runeBase, int characterID, int newUUID) { + super(newUUID); + this.runeBase = runeBase; + this.player = characterID; + if (this.runeBase != null) { + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); + this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID()); + } else { + this.skillsGranted = new ArrayList<>(); + this.powersGranted = new ArrayList<>(); + } + if (this.runeBase != null) + this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID()); + else + this.effectsGranted = new ArrayList<>(); + } + + /** + * ResultSet Constructor + */ + public CharacterRune(ResultSet rs) throws SQLException { + super(rs); + + this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID")); + this.player = rs.getInt("CharacterID"); + if (this.runeBase != null) { + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); + this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID()); + this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID()); + } else { + Logger.error("Failed to find RuneBase for CharacterRune " + this.getObjectUUID()); + this.skillsGranted = new ArrayList<>(); + this.powersGranted = new ArrayList<>(); + this.effectsGranted = new ArrayList<>(); + } + } + + public static void serializeForClientMsg(CharacterRune characterRune, ByteBufferWriter writer) { + if (characterRune.runeBase != null) { + int idd = characterRune.runeBase.getObjectUUID(); + if (idd > 3000 && idd < 3050) + writer.putInt(4); + else + writer.putInt(5); + // writer.putInt(this.runeBase.getMessageType()); + writer.putInt(0); + writer.putInt(characterRune.runeBase.getObjectUUID()); + writer.putInt(characterRune.getObjectType().ordinal()); + writer.putInt(characterRune.getObjectUUID()); + } else { + for (int i = 0; i < 5; i++) + writer.putInt(0); + } + } + + public static boolean grantRune(PlayerCharacter pc, int runeID) { + //Verify not too many runes + ArrayList runes = pc.getRunes(); + boolean worked = false; + synchronized (runes) { + if (runes == null || runes.size() > 12) + return false; + + //Verify player doesn't already have rune + for (CharacterRune rune : runes) { + RuneBase rb = rune.runeBase; + if (rb == null || rb.getObjectUUID() == runeID) + return false; + } + + RuneBase rb = RuneBase.getRuneBase(runeID); + if (rb == null) + return false; + + //Attempt to add rune to database + CharacterRune toAdd = new CharacterRune(rb, pc.getObjectUUID()); + CharacterRune rune = null; + try { + rune = DbManager.CharacterRuneQueries.ADD_CHARACTER_RUNE(toAdd); + } catch (Exception e) { + return false; + } + if (rune == null) + return false; + + //attempt add rune to player + worked = pc.addRune(rune); + + //worked, send ApplyRuneMsg + if (worked) { + ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), runeID, rune.getObjectType().ordinal(), rune.getObjectUUID(), true); + DispatchMessage.dispatchMsgToInterestArea(pc, arm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + CharacterSkill.calculateSkills(pc); + pc.applyBonuses(); + return true; + } else + return false; + } + + } + + public static boolean removeRune(PlayerCharacter pc, int runeID) { + ArrayList runes = pc.getRunes(); + synchronized (runes) { + for (CharacterRune rune : runes) { + RuneBase rb = rune.runeBase; + if (rb == null) + continue; + if (rb.getObjectUUID() == runeID && DbManager.CharacterRuneQueries.DELETE_CHARACTER_RUNE(rune)) { + runes.remove(runes.indexOf(rune)); + CharacterSkill.calculateSkills(pc); + pc.applyBonuses(); + return true; + } + } + return false; + } + } + + /* + * Getters + */ + public RuneBase getRuneBase() { + return this.runeBase; + } + + public int getRuneBaseID() { + if (this.runeBase != null) + return this.runeBase.getObjectUUID(); + return 0; + } + + public int getPlayerID() { + return this.player; + } + + /* + * Serializing + */ + + public ArrayList getSkillsGranted() { + return this.skillsGranted; + } + + public ArrayList getPowersGranted() { + return this.powersGranted; + } + + public ArrayList getEffectsGranted() { + return this.effectsGranted; + } + + @Override + public void updateDatabase() { + DbManager.CharacterRuneQueries.updateDatabase(this); + } +} diff --git a/src/engine/objects/CharacterSkill.java b/src/engine/objects/CharacterSkill.java new file mode 100644 index 00000000..a4cb9588 --- /dev/null +++ b/src/engine/objects/CharacterSkill.java @@ -0,0 +1,1237 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.CharacterSkills; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class CharacterSkill extends AbstractGameObject { + + private static final int[] maxTrains = { + 29, 29, 29, 29, 29, //0 to 4 + 29, 32, 34, 36, 38, //5 to 9 + 40, 42, 43, 45, 47, //10 to 14 + 48, 49, 51, 52, 53, //15 to 19 + 55, 56, 57, 58, 59, //20 to 24 + 60, 62, 63, 64, 65, //25 to 29 + 66, 67, 68, 68, 69, //30 to 34 + 70, 71, 72, 73, 74, //35 to 39 + 75, 76, 76, 77, 78, //40 to 44 + 79, 80, 80, 81, 82, //45 to 49 + 83, 83, 84, 85, 85, //50 to 54 + 86, 87, 88, 88, 89, //55 to 59 + 90, 90, 91, 92, 92, //60 to 64 + 93, 94, 94, 95, 95, //65 to 69 + 96, 97, 97, 98, 99, //70 to 74 + 99, 100, 100, 101, 101, //75 to 79 + 102, 103, 103, 104, 104, //80 to 84 + 105, 105, 106, 106, 107, //85 to 89 + 108, 109, 109, 110, 110, //90 to 94 + 111, 112, 112, 113, 113, //95 to 99 + 114, 115, 115, 116, 116, //100 to 104 + 117, 118, 118, 119, 119, //105 to 109 + 120, 121, 121, 122, 122, //110 to 114 + 123, 124, 124, 125, 125, //115 to 119 + 126, 127, 127, 128, 128, //120 to 124 + 129, 130, 130, 131, 131, //125 to 129 + 132, 133, 133, 134, 134, //130 to 134 + 135, 136, 136, 137, 137, //135 to 139 + 138, 139, 139, 140, 140, //140 to 144 + 141, 142, 142, 143, 143, //145 to 149 + 144, 145, 145, 146, 146, //150 to 154 + 147, 148, 148, 149, 149, //155 to 159 + 150, 151, 151, 152, 152, //160 to 164 + 153, 154, 154, 155, 155, //165 to 169 + 156, 157, 157, 158, 158, //170 to 174 + 159, 160, 160, 161, 161, //175 to 179 + 162, 163, 163, 164, 164, //180 to 184 + 165, 166, 166, 167, 167, //185 to 189 + 168}; //190 + + private static final float[] baseSkillValues = { + 0.0f, 0.0f, 0.2f, 0.4f, 0.6f, //0 to 4 + 0.8f, 1.0f, 1.1666666f, 1.3333334f, 1.5f, //5 to 9 + 1.6666667f, 1.8333334f, 2.0f, 2.2f, 2.4f, //10 to 14 + 2.6f, 2.8f, 3.0f, 3.2f, 3.4f, //15 to 19 + 3.6f, 3.8f, 4.0f, 4.2f, 4.4f, //20 to 24 + 4.6f, 4.8f, 5.0f, 5.25f, 5.5f, //25 to 29 + 5.75f, 6.0f, 6.2f, 6.4f, 6.6f, //30 to 34 + 6.8f, 7.0f, 7.25f, 7.5f, 7.75f, //35 to 39 + 8.0f, 8.2f, 8.4f, 8.6f, 8.8f, //40 to 44 + 9.0f, 9.25f, 9.5f, 9.75f, 10.0f, //45 to 49 + 10.25f, 10.5f, 10.75f, 11.0f, 11.2f, //50 to 54 + 11.4f, 11.6f, 11.8f, 12.0f, 12.25f, //55 to 59 + 12.5f, 12.75f, 13.0f, 13.25f, 13.5f, //60 to 64 + 13.75f, 14.0f, 14.25f, 14.5f, 14.75f, //65 to 69 + 15.0f, 15.333333f, 15.666667f, 16.0f, 16.25f, //70 to 74 + 16.5f, 16.75f, 17.0f, 17.25f, 17.5f, //75 to 79 + 17.75f, 18.0f, 18.25f, 18.5f, 18.75f, //80 to 84 + 19.0f, 19.333334f, 19.666666f, 20.0f, 20.25f, //85 to 89 + 20.5f, 20.75f, 21.0f, 21.25f, 21.5f, //90 to 94 + 21.75f, 22.0f, 22.333334f, 22.666666f, 23.0f, //95 to 99 + 23.25f, 23.5f, 23.75f, 24.0f, 24.333334f, //100 to 104 + 24.666666f, 25.0f, 25.25f, 25.5f, 25.75f, //105 to 109 + 26.0f, 26.333334f, 26.666666f, 27.0f, 27.333334f, //110 to 114 + 27.666666f, 28.0f, 28.25f, 28.5f, 28.75f, //115 to 119 + 29.0f, 29.333334f, 29.666666f, 30.0f, 30.333334f, //120 to 124 + 30.666666f, 31.0f, 31.25f, 31.5f, 31.75f, //125 to 129 + 32.0f, 32.333332f, 32.666668f, 33.0f, 33.333332f, //130 to 134 + 33.666668f, 34.0f, 34.333332f, 34.666668f, 35.0f, //135 to 139 + 35.333332f, 35.666668f, 36.0f, 36.333332f, 36.666668f, //140 to 144 + 37.0f, 37.25f, 37.5f, 37.75f, 38.0f, //145 to 149 + 38.333332f, 38.666668f, 39.0f, 39.333332f, 39.666668f, //150 to 154 + 40.0f, 40.333332f, 40.666668f, 41.0f, 41.333332f, //155 to 159 + 41.666668f, 42.0f, 42.333332f, 42.666668f, 43.0f, //160 to 164 + 43.333332f, 43.666668f, 44.0f, 44.333332f, 44.666668f, //165 to 169 + 45.0f, 45.5f, 46.0f, 46.333332f, 46.666668f, //170 to 174 + 47.0f, 47.333332f, 47.666668f, 48.0f, 48.333332f, //175 to 179 + 48.666668f, 49.0f, 49.333332f, 49.666668f, 50.0f, //180 to 184 + 50.333332f, 50.666668f, 51.0f, 51.333332f, 51.666668f, //185 to 189 + 52.0f, 52.5f, 53.0f, 53.333332f, 53.666668f, //190 to 194 + 54.0f, 54.333332f, 54.666668f, 55.0f, 55.333332f, //195 to 199 + 55.666668f, 56.0f, 56.333332f, 56.666668f, 57.0f, //200 to 204 + 57.5f, 58.0f, 58.333332f, 58.666668f, 59.0f, //205 to 209 + 59.333332f, 59.666668f, 60.0f, 60.5f, 61.0f, //210 to 214 + 61.333332f, 61.666668f, 62.0f, 62.333332f, 62.666668f, //215 to 219 + 63.0f, 63.5f, 64.0f, 64.333336f, 64.666664f, //220 to 224 + 65.0f, 65.333336f, 65.666664f, 66.0f, 66.5f, //225 to 229 + 67.0f, 67.333336f, 67.666664f, 68.0f, 68.5f, //230 to 234 + 69.0f, 69.333336f, 69.666664f, 70.0f, 70.333336f, //235 to 239 + 70.666664f, 71.0f, 71.5f, 72.0f, 72.5f, //240 to 244 + 73.0f, 73.333336f, 73.666664f, 74.0f, 74.333336f, //245 to 249 + 74.666664f, 75.0f, 75.5f, 76.0f, 76.333336f, //250 to 254 + 76.666664f, 77.0f, 77.5f, 78.0f, 78.333336f, //255 to 259 + 78.666664f, 79.0f, 79.5f, 80.0f, 80.333336f, //260 to 264 + 80.666664f, 81.0f, 81.5f, 82.0f, 82.333336f, //265 to 269 + 82.666664f, 83.0f, 83.5f, 84.0f, 84.333336f, //270 to 274 + 84.666664f, 85.0f, 85.5f, 86.0f, 86.5f, //275 to 279 + 87.0f, 87.333336f, 87.666664f, 88.0f, 88.5f, //280 to 284 + 89.0f, 89.333336f, 89.666664f, 90.0f, 90.5f, //285 to 289 + 91.0f, 91.5f, 92.0f, 92.333336f, 92.666664f, //290 to 294 + 93.0f, 93.5f, 94.0f, 94.5f, 95.0f, //295 to 299 + 95.333336f, 95.666664f, 96.0f, 96.5f, 97.0f, //300 to 304 + 97.5f, 98.0f, 98.333336f, 98.666664f, 99.0f, //305 to 309 + 99.5f, 100.0f, 100.5f, 101.0f, 101.5f, //310 to 314 + 102.0f, 102.5f, 103.0f, 103.333336f, 103.666664f, //315 to 319 + 104.0f, 104.333336f, 104.666664f, 105.0f, 105.5f, //320 to 324 + 106.0f, 106.5f, 107.0f, 108.0f, 108.333336f, //325 to 329 + 108.666664f, 109.0f, 109.333336f, 109.666664f, 110.0f, //330 to 334 + 110.5f, 111.0f, 111.5f, 112.0f, 112.5f, //335 to 339 + 113.0f, 113.333336f, 113.666664f, 114.0f, 114.5f, //340 to 344 + 115.0f, 115.5f, 116.0f, 116.5f, 117.0f, //345 to 349 + 117.5f, 118.0f, 118.333336f, 118.666664f, 119.0f, //350 to 354 + 119.5f, 120.0f, 120.5f, 121.0f, 121.5f, //355 to 359 + 122.0f, 122.5f, 123.0f, 123.333336f, 123.666664f, //360 to 364 + 124.0f, 124.5f, 125.0f, 125.5f, 126.0f, //365 to 369 + 126.5f, 127.0f, 127.5f, 128.0f, 128.5f, //370 to 374 + 129.0f, 129.5f, 130.0f, 130.33333f, 130.66667f, //375 to 379 + 131.0f, 131.5f, 132.0f, 132.5f, 133.0f, //380 to 384 + 133.5f, 134.0f, 134.5f, 135.0f, 135.5f, //385 to 389 + 136.0f, 136.5f, 137.0f, 137.5f, 138.0f, //390 to 394 + 138.5f, 139.0f, 139.5f, 140.0f, 140.33333f, //395 to 399 + 140.66667f, 141.0f, 141.5f, 142.0f, 142.5f, //400 to 404 + 143.0f, 143.5f, 144.0f, 144.5f, 145.0f, //405 to 409 + 145.5f, 146.0f, 146.5f, 147.0f, 147.5f, //410 to 414 + 148.0f, 148.5f, 149.0f, 149.5f, 150.0f, //415 to 419 + 150.5f, 151.0f, 151.5f, 152.0f, 152.5f, //420 to 424 + 153.0f, 153.5f, 154.0f, 154.5f, 155.0f, //425 to 429 + 155.5f, 156.0f, 156.5f, 157.0f, 157.5f, //430 to 434 + 158.0f, 158.5f, 159.0f, 159.5f, 160.0f, //435 to 439 + 160.5f, 161.0f, 161.5f, 162.0f, 162.5f, //440 to 444 + 163.0f, 163.5f, 164.0f, 164.5f, 165.0f, //445 to 449 + 165.5f, 166.0f, 166.5f, 167.0f, 167.5f, //450 to 454 + 168.0f, 168.5f, 169.0f, 169.5f, 170.0f, //455 to 459 + 170.5f, 171.0f, 171.5f, 172.0f, 172.5f, //460 to 464 + 173.0f, 173.5f, 174.0f, 174.5f, 175.0f, //465 to 469 + 176.0f, 176.5f, 177.0f, 177.5f, 178.0f, //470 to 474 + 178.5f, 179.0f, 179.5f, 180.0f, 180.5f, //475 to 479 + 181.0f, 181.5f, 182.0f, 182.5f, 183.0f, //480 to 484 + 183.5f, 184.0f, 184.5f, 185.0f, 185.5f, //485 to 489 + 186.0f, 187.0f, 187.5f, 188.0f, 188.5f, //490 to 494 + 189.0f, 189.5f, 190.0f, 190.5f, 191.0f, //495 to 499 + 191.5f, 192.0f, 192.5f, 193.0f, 193.5f, //500 to 504 + 194.0f, 194.5f, 195.0f, 196.0f, 196.5f, //505 to 509 + 197.0f, 197.5f, 198.0f, 198.5f, 199.0f, //510 to 514 + 199.5f, 200.0f, 200.5f, 201.0f, 201.5f, //515 to 519 + 202.0f, 203.0f, 203.5f, 204.0f, 204.5f, //520 to 524 + 205.0f, 205.5f, 206.0f, 206.5f, 207.0f, //525 to 529 + 207.5f, 208.0f, 209.0f, 209.5f, 210.0f, //530 to 534 + 210.5f, 211.0f, 211.5f, 212.0f, 212.5f, //535 to 539 + 213.0f, 214.0f, 214.5f, 215.0f, 215.5f, //540 to 544 + 216.0f, 216.5f, 217.0f, 217.5f, 218.0f, //545 to 549 + 218.5f, 219.0f, 220.0f, 220.5f, 221.0f, //550 to 554 + 221.5f, 222.0f, 222.5f, 223.0f, 224.0f, //555 to 559 + 224.5f, 225.0f, 225.5f, 226.0f, 226.5f, //560 to 564 + 227.0f, 227.5f, 228.0f, 229.0f, 229.5f, //565 to 569 + 230.0f, 230.5f, 231.0f, 231.5f, 232.0f, //570 to 574 + 233.0f, 233.5f, 234.0f, 234.5f, 235.0f, //575 to 579 + 235.5f, 236.0f, 237.0f, 237.5f, 238.0f, //580 to 584 + 238.5f, 239.0f, 239.5f, 240.0f, 241.0f, //585 to 589 + 241.5f, 242.0f, 242.5f, 243.0f, 243.5f, //590 to 594 + 244.0f, 245.0f, 245.5f, 246.0f, 246.5f, //595 to 599 + 247.0f}; //600 + + + private SkillsBase skillsBase; + private AtomicInteger numTrains = new AtomicInteger(); + + private CharacterSkills skillType; + + //Skill% before trains and before any effects or item bonuses + private float baseAmountBeforeMods; + + //Skill% after trains but before any effects or item bonuses + private float modifiedAmountBeforeMods; + private boolean isMobOwner = false; + + //Skill% before trains but after any effects or item bonuses + private float baseAmount; + + //Skill% after trains and after any effects or item bonuses + private float modifiedAmount; + + private int ownerUID; + private boolean trained = false; + private int requiredLevel = 0; + + /** + * No Table ID Constructor + */ + public CharacterSkill(SkillsBase skillsBase, PlayerCharacter pc) { + super(); + this.skillsBase = skillsBase; + this.numTrains.set(0); + this.ownerUID = pc.getObjectUUID(); + calculateBaseAmount(); + calculateModifiedAmount(); + this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken()); + } + + /** + * Normal Constructor + */ + public CharacterSkill(SkillsBase skillsBase, PlayerCharacter pc, int newUUID) { + + super(newUUID); + this.skillsBase = skillsBase; + this.numTrains.set(0); + this.ownerUID = pc.getObjectUUID(); + this.trained = true; + calculateBaseAmount(); + calculateModifiedAmount(); + this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken()); + + } + + /** + * ResultSet Constructor + */ + public CharacterSkill(ResultSet rs, PlayerCharacter pc) throws SQLException { + super(rs); + + int skillsBaseID = rs.getInt("SkillsBaseID"); + this.skillsBase = DbManager.SkillsBaseQueries.GET_BASE(skillsBaseID); + this.numTrains.set(rs.getShort("trains")); + this.ownerUID = pc.getObjectUUID(); + calculateBaseAmount(); + calculateModifiedAmount(); + this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken()); + } + + public CharacterSkill(SkillsBase sb, Mob mob, int trains) { + super(); + this.skillsBase = sb; + this.numTrains.set(trains); + this.ownerUID = mob.getObjectUUID(); + this.isMobOwner = true; + calculateMobBaseAmount(); + calculateModifiedAmount(); + this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken()); + } + + public CharacterSkill(ResultSet rs) throws SQLException { + super(rs); + int skillsBaseID = rs.getInt("SkillsBaseID"); + this.skillsBase = DbManager.SkillsBaseQueries.GET_BASE(skillsBaseID); + this.numTrains.set(rs.getShort("trains")); + this.ownerUID = rs.getInt("CharacterID"); + // this.owner = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(rs.getInt("CharacterID")); + calculateBaseAmount(); + calculateModifiedAmount(); + this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken()); + } + + public static AbstractCharacter GetOwner(CharacterSkill cs) { + if (cs.ownerUID == 0) + return null; + if (cs.isMobOwner) + return Mob.getFromCache(cs.ownerUID); + else + return PlayerCharacter.getFromCache(cs.ownerUID); + } + + /* + * Getters + */ + + public static float getATR(AbstractCharacter ac, String name) { + if (ac == null) + return 0f; + float atr; + ConcurrentHashMap skills = ac.getSkills(); + CharacterSkill skill = skills.get(name); + if (skill != null) + atr = skill.getATR(ac); + else { + float mast = CharacterSkill.getQuickMastery(ac, name); + atr = (((int) mast * 7) + (ac.getStatDexCurrent() / 2)); + } + //apply effect mods + PlayerBonuses bonus = ac.getBonuses(); + if (bonus == null) + return atr; + atr += bonus.getFloat(ModType.OCV, SourceType.None); + float pos_Bonus = bonus.getFloatPercentPositive(ModType.OCV, SourceType.None); + atr *= (1 + pos_Bonus); + //rUNES will already be applied + // atr *= (1 + ((float)bonus.getShort("rune.Attack") / 100)); //precise + float neg_Bonus = bonus.getFloatPercentNegative(ModType.OCV, SourceType.None); + atr *= (1 + neg_Bonus); + return atr; + } + + public static void serializeForClientMsg(CharacterSkill characterSkill, ByteBufferWriter writer) { + if (characterSkill.skillsBase == null) { + Logger.error("SkillsBase not found for skill " + characterSkill.getObjectUUID()); + writer.putInt(0); + writer.putInt(0); + } else { + writer.putInt(characterSkill.skillsBase.getToken()); + writer.putInt(characterSkill.numTrains.get()); + } + } + + /** + * @ This updates all Base Skill Amouts for a player + * Convienence method + */ + public static void updateAllBaseAmounts(PlayerCharacter pc) { + if (pc == null) + return; + ConcurrentHashMap skills = pc.getSkills(); + Iterator it = skills.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + CharacterSkill cs = skills.get(name); + if (cs != null) + cs.calculateBaseAmount(); + // Logger.info("CharacterSkill", pc.getName() + ", skill: " + + // cs.getSkillsBase().getName() + ", trains: " + cs.numTrains + + // ", base: " + cs.baseAmount + ", mod: " + cs.modifiedAmount); + } + + //Recalculate ATR, damage and defense + //pc.calculateAtrDefenseDamage(); + + //recalculate movement bonus + //pc.calculateSpeedMod(); + } + + /** + * @ This updates all Modified skill Amounts for a player + * Convienence method + */ + public static void updateAllModifiedAmounts(PlayerCharacter pc) { + if (pc == null) + return; + ConcurrentHashMap skills = pc.getSkills(); + Iterator it = skills.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + CharacterSkill cs = skills.get(name); + if (cs != null) + cs.calculateModifiedAmount(); + + } + + //Recalculate ATR, damage and defense + //pc.calculateAtrDefenseDamage(); + } + + public static int getTrainsAvailable(PlayerCharacter pc) { + + if (pc == null) + return 0; + if (pc.getRace() == null || pc.getBaseClass() == null) { + Logger.error("Race or BaseClass not found for player " + pc.getObjectUUID()); + return 0; + } + int raceBonus = 0; + int baseMod = 0; + int promoMod = 6; + int available = 0; + + //get racial bonus; + if (pc.getRace().getRaceType().equals(Enum.RaceType.HUMANMALE) || + pc.getRace().getRaceType().equals(Enum.RaceType.HUMANFEMALE)) + raceBonus = 1; //Human racial bonus; + + //get base class trains + if (pc.getBaseClass().getObjectUUID() == 2500 || pc.getBaseClass().getObjectUUID() == 2502) { + baseMod = 4; //Fighter or Rogue + } else { + baseMod = 5; //Healer or Mage + } + + int level = pc.getLevel(); + if (level > 74) + available = 62 + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus)); + else if (level > 69) + available = ((level - 69) * 3) + 45 + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus)); + else if (level > 64) + available = ((level - 64) * 4) + 25 + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus)); + else if (level > 59) //Between 60 and 65 + available = ((level - 59) * 5) + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus)); + else if (level > 10) //Between 11 and 59 + available = ((level - 10) * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus)); + // available = (level - 59) + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus)); + else if (level == 10 && (pc.getPromotionClass() != null)) //10 but promoted + available = (promoMod + baseMod + raceBonus) + (9 * (baseMod + raceBonus)); + else //not promoted + available = (level - 1) * (baseMod + raceBonus); + + //next subtract trains in any skills + ConcurrentHashMap skills = pc.getSkills(); + Iterator it = skills.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + CharacterSkill cs = skills.get(name); + if (cs != null) + available -= cs.numTrains.get(); + } + + //TODO subtract any trains from powers + ConcurrentHashMap powers = pc.getPowers(); + for (CharacterPower power : powers.values()) { + if (power != null) + available -= power.getTrains(); + } + + if (MBServerStatics.BONUS_TRAINS_ENABLED) { + available += 1000; + } + + // if (available < 0) { + //TODO readd this error log after test + // Logger.error("CharacterSkill.getTrainsAvailable", "Number of trains available less then 0 for Player " + pc.getUUID()); + // available = 0; + // } + + //return what's left + return available; + } + + /** + * @ Returns mastery base when mastery not granted + * to player. For calculating damage correctly + */ + public static float getQuickMastery(AbstractCharacter pc, String mastery) { + SkillsBase sb = SkillsBase.getFromCache(mastery); + if (sb == null) { + sb = DbManager.SkillsBaseQueries.GET_BASE_BY_NAME(mastery); + if (sb == null) { + //Logger.error("CharacterSkill.getQuickMastery", "Unable to find skillsbase of name " + mastery); + return 0f; + } + } + + float bonus = 0f; + SourceType sourceType = SourceType.GetSourceType(sb.getNameNoSpace()); + if (pc.getBonuses() != null) { + //Get bonuses from runes + bonus = pc.getBonuses().getSkillBonus(sb.sourceType); + } + float base = 4.75f; + base += (0.0025f * sb.getStrMod() * pc.getStatStrCurrent()); + base += (0.0025f * sb.getDexMod() * pc.getStatDexCurrent()); + base += (0.0025f * sb.getConMod() * pc.getStatConCurrent()); + base += (0.0025f * sb.getIntMod() * pc.getStatIntCurrent()); + base += (0.0025f * sb.getSpiMod() * pc.getStatSpiCurrent()); + return base + bonus; + } + + /* + * This iterates through players runes and adds and removes skills as needed + * Don't Call this directly. Instead call pc.calculateSkills(). + */ + public static void calculateSkills(PlayerCharacter pc) { + if (pc == null) + return; + + ConcurrentHashMap skills = pc.getSkills(); + + //First add skills that don't exist + Race race = pc.getRace(); + if (race != null) { + CharacterSkill.grantSkills(race.getSkillsGranted(), pc); + } else + Logger.error("Failed to find Race for player " + pc.getObjectUUID()); + BaseClass bc = pc.getBaseClass(); + if (bc != null) { + CharacterSkill.grantSkills(bc.getSkillsGranted(), pc); + } else + Logger.error("Failed to find BaseClass for player " + pc.getObjectUUID()); + PromotionClass promo = pc.getPromotionClass(); + if (promo != null) + CharacterSkill.grantSkills(promo.getSkillsGranted(), pc); + ArrayList runes = pc.getRunes(); + if (runes != null) { + for (CharacterRune rune : runes) { + CharacterSkill.grantSkills(rune.getSkillsGranted(), pc); + } + } else + Logger.error("Failed to find Runes list for player " + pc.getObjectUUID()); + + //next remove any skills that no longer belong + Iterator it = skills.values().iterator(); + while (it.hasNext()) { + CharacterSkill cs = it.next(); + if (cs == null) + continue; + SkillsBase sb = cs.skillsBase; + if (sb == null) { + DbManager.CharacterSkillQueries.DELETE_SKILL(cs.getObjectUUID()); + it.remove(); + continue; + } + boolean valid = false; + if (CharacterSkill.skillAllowed(sb.getObjectUUID(), race.getSkillsGranted(), pc)) + continue; + if (CharacterSkill.skillAllowed(sb.getObjectUUID(), bc.getSkillsGranted(), pc)) + continue; + if (promo != null) + if (CharacterSkill.skillAllowed(sb.getObjectUUID(), promo.getSkillsGranted(), pc)) + continue; + for (CharacterRune rune : runes) { + if (CharacterSkill.skillAllowed(sb.getObjectUUID(), rune.getSkillsGranted(), pc)) { + valid = true; + continue; + } + } + //if skill doesn't belong to any runes, then remove it + if (!valid) { + DbManager.CharacterSkillQueries.DELETE_SKILL(cs.getObjectUUID()); + it.remove(); + } + } + CharacterSkill.updateAllBaseAmounts(pc); + CharacterSkill.updateAllModifiedAmounts(pc); + CharacterPower.calculatePowers(pc); + } + + /* + *This grants skills for specific runes + */ + private static void grantSkills(ArrayList skillsGranted, PlayerCharacter pc) { + ConcurrentHashMap skills = pc.getSkills(); + if (skills == null) + return; + + for (SkillReq skillreq : skillsGranted) { + SkillsBase skillsBase = skillreq.getSkillsBase(); + + //If player not high enough level for skill, then skip + if (pc.getLevel() < skillreq.getLevel()) + continue; + //If player doesn't have prereq skills high enough then skip + boolean valid = true; + for (byte prereqSkill : skillreq.getSkillReqs()) { + SkillsBase sb = null; + sb = DbManager.SkillsBaseQueries.GET_BASE(prereqSkill); + if (sb != null) { + if (skills.containsKey(sb.getName())) { + if (validForWarrior(pc, skills.get(sb.getName()), skillreq)) { + valid = true; + break; //add if any prereq skills met + } else if (skills.get(sb.getName()).modifiedAmountBeforeMods >= 80) { + valid = true; + break; //add if any prereq skills met + // allow blade masters to use blade master without training sword above 80.. + } else if (skillsBase.getObjectUUID() == 9) { + valid = true; + break; + } + + } + } else { + Logger.error("Failed to find SkillsBase of ID " + prereqSkill); + } + valid = false; + } + // Throwing does not need axe,dagger, or hammer at 80% + if (skillreq.getSkillID() == 43) + valid = true; + if (!valid) + continue; + + //Skill valid for player. Add if don't already have + if (skillsBase != null) { + if (!skills.containsKey(skillsBase.getName())) { + CharacterSkill newSkill = new CharacterSkill(skillsBase, pc); + CharacterSkill cs = null; + try { + cs = DbManager.CharacterSkillQueries.ADD_SKILL(newSkill); + } catch (Exception e) { + cs = null; + } + if (cs != null) { + cs.requiredLevel = (int) skillreq.getLevel(); + skills.put(skillsBase.getName(), cs); + } else + Logger.error("Failed to add CharacterSkill to player " + pc.getObjectUUID()); + } else { + CharacterSkill cs = skills.get(skillsBase.getName()); + if (cs != null && cs.requiredLevel == 0) { + cs.requiredLevel = (int) skillreq.getLevel(); + } + } + + } else + Logger.error("Failed to find SkillsBase for SkillReq " + skillreq.getObjectUUID()); + } + } + + private static boolean validForWarrior(PlayerCharacter pc, CharacterSkill skill, SkillReq skillreq) { + if (pc.getPromotionClass() == null || pc.getPromotionClass().getObjectUUID() != 2518 || skill == null || skillreq == null) + return false; //not a warrior + int sID = (skill.skillsBase != null) ? skill.skillsBase.getObjectUUID() : 0; + switch (skillreq.getSkillID()) { + case 3: //Axe mastery + case 19: //Great axe mastery + return (sID == 4) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 15: //Dagger mastery + return (sID == 16) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 20: //Great hammer mastery + case 22: //Hammer mastery + return (sID == 23) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 28: //Polearm mastery + return (sID == 29) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 21: //Great sword mastery + case 39: //Sword mastery + return (sID == 40) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 34: //Spear mastery + return (sID == 35) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 36: //Staff mastery + return (sID == 37) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 45: //Unarmed combat mastery + return (sID == 46) ? (skill.modifiedAmountBeforeMods >= 50) : false; + case 40: + return true; + case 9: + return true; + default: + } + return false; + } + + /* + * This verifies if a skill is valid for a players rune + */ + private static boolean skillAllowed(int UUID, ArrayList skillsGranted, PlayerCharacter pc) { + ConcurrentHashMap skills = pc.getSkills(); + for (SkillReq skillreq : skillsGranted) { + SkillsBase sb = skillreq.getSkillsBase(); + if (sb != null) { + if (sb.getObjectUUID() == UUID) { + if (skillreq.getLevel() <= pc.getLevel()) { + SkillsBase sbp = null; + if (skillreq.getSkillReqs().size() == 0) + return true; + for (byte prereqSkill : skillreq.getSkillReqs()) { + sbp = DbManager.SkillsBaseQueries.GET_BASE(prereqSkill); + if (sbp != null && skills.containsKey(sbp.getName())) { + if (validForWarrior(pc, skills.get(sbp.getName()), skillreq)) { + return true; + } else if (skills.get(sbp.getName()).modifiedAmountBeforeMods >= 80) + return true; + } + } + + if (skillreq.getSkillID() == 43) + return true; + if (skillreq.getSkillID() == 9) + return true; + } + } + } else + Logger.error("Failed to find SkillsBase for SkillReq " + skillreq.getObjectUUID()); + } + return false; + } + + //Print skills for player for debugging + public static void printSkills(PlayerCharacter pc) { + if (pc == null) + return; + ConcurrentHashMap skills = pc.getSkills(); + String out = "Player: " + pc.getObjectUUID() + ", SkillCount: " + skills.size(); + Iterator it = skills.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + out += ", " + name; + } + Logger.info(out); + } + + public static int getMaxTrains(int intt) { + if (intt > 0 && intt < 191) + return CharacterSkill.maxTrains[intt]; + else + return (int) (33 + 1.25 * intt - 0.005 * Math.pow(intt, 2)); + } + + private float getATR(AbstractCharacter ac) { + return (((int) this.modifiedAmount * 7) + (ac.getStatDexCurrent() / 2)); + } + + public synchronized boolean train(PlayerCharacter pc) { + if (pc == null || this.skillsBase == null) + return false; + + boolean running = false; + + //trying out a table lookup + int intt = (int) pc.statIntBase; + int maxTrains = 0; + if (intt > 0 && intt < 191) + maxTrains = CharacterSkill.maxTrains[intt]; + else + maxTrains = (int) (33 + 1.25 * (int) pc.statIntBase - 0.005 * Math.pow((int) pc.statIntBase, 2)); + + + int oldTrains = this.numTrains.get(); + boolean succeeded = true; + if (pc.getTrainsAvailable() <= 0) + return false; + if (oldTrains == maxTrains) //at gold, stop here + return false; + else if (oldTrains > maxTrains) //catch incase we somehow go over + this.numTrains.set(maxTrains); + else //add the train + succeeded = this.numTrains.compareAndSet(oldTrains, oldTrains + 1); + + if (this.numTrains.get() > maxTrains) { //double check not over max trains + this.numTrains.set(maxTrains); + succeeded = false; + } + + if (succeeded) { + this.trained = true; + + //subtract from trains available + pc.modifyTrainsAvailable(-1); + + //update database + pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS); + + //recalculate this skill + calculateBaseAmount(); + calculateModifiedAmount(); + + //see if any new skills or powers granted + pc.calculateSkills(); + + //reapply all bonuses + pc.applyBonuses(); + + //update cache if running trains change + if (running) + pc.incVer(); + + return true; + } else + return false; + } + + public float getTrainingCost(PlayerCharacter pc, NPC trainer) { + int charLevel = pc.getLevel(); + int skillRank = this.getNumTrains() - 1 + this.requiredLevel; + float baseCost = 15 * this.requiredLevel; //TODO GET BASE COSTS OF SKILLS. + + + float sellPercent = -.20f; + float cost; + float const5; + int const2 = 1; + float const3 = 50; + float const4 = const3 + const2; + + if (charLevel > 50) + const5 = 50 / const4; + else + const5 = charLevel / const4; + + const5 = 1 - const5; + const5 = (float) (Math.log(const5) / Math.log(2) * .75f); + float rounded5 = Math.round(const5); + const5 = rounded5 - const5; + + const5 *= -1; + + const5 = (float) (Math.pow(2, const5) - 1); + + const5 += 1; + const5 = Math.scalb(const5, (int) rounded5); + const5 *= (charLevel - skillRank); + const5 *= sellPercent; + const5 = (float) (Math.log(const5) / Math.log(2) * 3); + rounded5 = Math.round(const5); + const5 = rounded5 - const5; + const5 *= -1; + const5 = (float) (Math.pow(2, const5) - 1); + const5 += 1; + + + const5 = Math.scalb(const5, (int) rounded5); + const5 += 1; + cost = const5 * (baseCost); + + + if (Float.isNaN(cost)) + cost = baseCost; + return cost; + } + + //Call this to refine skills and recalculate everything for pc. + public boolean refine(PlayerCharacter pc) { + return refine(pc, true); + } + + public boolean refine(PlayerCharacter pc, boolean recalculate) { + if (pc == null || this.skillsBase == null) + return false; + + int oldTrains = this.numTrains.get(); + boolean succeeded = true; + if (this.getNumTrains() < 1) + return false; + succeeded = this.numTrains.compareAndSet(oldTrains, oldTrains - 1); + if (succeeded) { + this.trained = true; + + //add to trains available + pc.modifyTrainsAvailable(1); + + //update database + pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS); + + if (recalculate) { + //recalculate this skill + calculateBaseAmount(); + calculateModifiedAmount(); + + //see if any skills or powers removed + pc.calculateSkills(); + + //reapply all bonuses + pc.applyBonuses(); + } + + return true; + } else + return false; + } + + /* + * Serializing + */ + + public boolean reset(PlayerCharacter pc, boolean recalculate) { + if (pc == null || this.skillsBase == null) + return false; + + int oldTrains = this.numTrains.get(); + boolean succeeded = true; + if (this.getNumTrains() < 1) + return false; + succeeded = this.numTrains.compareAndSet(oldTrains, 0); + if (succeeded) { + this.trained = true; + + //add to trains available + pc.modifyTrainsAvailable(oldTrains); + + //update database + pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS); + + if (recalculate) { + //recalculate this skill + calculateBaseAmount(); + calculateModifiedAmount(); + + //see if any skills or powers removed + pc.calculateSkills(); + + //reapply all bonuses + pc.applyBonuses(); + } + + return true; + } else + return false; + } + + /* + * Returns Skill Base for skill + */ + public SkillsBase getSkillsBase() { + return this.skillsBase; + } + + /* + * Returns number of trains in skill + */ + public int getNumTrains() { + return this.numTrains.get(); + } + + /* + * Returns Skill% before trains added + */ + public float getBaseAmount() { + return this.baseAmount; + } + + /* + * Returns Skill% after trains added + */ + public float getModifiedAmount() { + return this.modifiedAmount; + } + + /* + * Returns Skill% before trains added, minus bonus from equip and effects + */ + public float getBaseAmountBeforeMods() { + return this.baseAmountBeforeMods; + } + + /* + * Returns Skill% after trains added, minus bonus from equip and effects + */ + public float getModifiedAmountBeforeMods() { + return this.modifiedAmountBeforeMods; + } + + public String getName() { + if (this.skillsBase != null) + return this.skillsBase.getName(); + return ""; + } + + public int getToken() { + if (this.skillsBase != null) + return this.skillsBase.getToken(); + return 0; + } + + public boolean isTrained() { + return trained; + } + + public void syncTrains() { + this.trained = false; + } + + /** + * @ Calculates Base Skill Percentage + * Call this when stats change for a player + */ + public void calculateBaseAmount() { + if (CharacterSkill.GetOwner(this) == null) { + Logger.error("owner not found for owner uuid : " + this.ownerUID); + this.baseAmount = 1; + this.modifiedAmount = 1; + return; + } + + if (this.skillsBase == null) { + Logger.error("SkillsBase not found for skill " + this.getObjectUUID()); + this.baseAmount = 1; + this.modifiedAmount = 1; + return; + } + + //Get any rune bonus + float bonus = 0f; + //runes will already be calculated + if (CharacterSkill.GetOwner(this).getBonuses() != null) { + //Get bonuses from runes + bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType); + } + + //Get Base skill for unmodified stats + float base = 7f; + float statMod = 0.5f; + if (this.skillsBase.getStrMod() > 0) + statMod += (float) this.skillsBase.getStrMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statStrBase / 100f; + if (this.skillsBase.getDexMod() > 0) + statMod += (float) this.skillsBase.getDexMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statDexBase / 100f; + if (this.skillsBase.getConMod() > 0) + statMod += (float) this.skillsBase.getConMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statConBase / 100f; + if (this.skillsBase.getIntMod() > 0) + statMod += (float) this.skillsBase.getIntMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statIntBase / 100f; + if (this.skillsBase.getSpiMod() > 0) + statMod += (float) this.skillsBase.getSpiMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statSpiBase / 100f; + if (statMod < 1) + statMod = 1f; + else if (statMod > 600) + statMod = 600f; + base += CharacterSkill.baseSkillValues[(int) statMod]; + + if (base + bonus < 1f) + this.baseAmountBeforeMods = 1f; + else + this.baseAmountBeforeMods = base + bonus; + this.modifiedAmountBeforeMods = Math.round(this.baseAmountBeforeMods + calculateAmountAfterTrains()); + } + + public void calculateMobBaseAmount() { + if (CharacterSkill.GetOwner(this) == null) { + Logger.error("owner not found for owner uuid : " + this.ownerUID); + this.baseAmount = 1; + this.modifiedAmount = 1; + return; + } + + if (this.skillsBase == null) { + Logger.error("SkillsBase not found for skill " + this.getObjectUUID()); + this.baseAmount = 1; + this.modifiedAmount = 1; + return; + } + + //Get any rune bonus + float bonus = 0f; + //TODO SKILLS RUNES + + if (CharacterSkill.GetOwner(this).getBonuses() != null) { + //Get bonuses from runes + bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType); + } + + //Get Base skill for unmodified stats + float base = 7f; + float statMod = 0.5f; + if (this.skillsBase.getStrMod() > 0) + statMod += (float) this.skillsBase.getStrMod() * (float) ((Mob) CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseStr() / 100f; + if (this.skillsBase.getDexMod() > 0) + statMod += (float) this.skillsBase.getDexMod() * (float) ((Mob) CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseDex() / 100f; + if (this.skillsBase.getConMod() > 0) + statMod += (float) this.skillsBase.getConMod() * (float) ((Mob) CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseCon() / 100f; + if (this.skillsBase.getIntMod() > 0) + statMod += (float) this.skillsBase.getIntMod() * (float) ((Mob) CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseInt() / 100f; + if (this.skillsBase.getSpiMod() > 0) + statMod += (float) this.skillsBase.getSpiMod() * (float) ((Mob) CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseSpi() / 100f; + if (statMod < 1) + statMod = 1f; + else if (statMod > 600) + statMod = 600f; + base += CharacterSkill.baseSkillValues[(int) statMod]; + + if (base + bonus < 1f) + this.baseAmountBeforeMods = 1f; + else + this.baseAmountBeforeMods = base + bonus; + this.modifiedAmountBeforeMods = (int) (this.baseAmountBeforeMods + calculateAmountAfterTrains()); + } + + public void calculateModifiedAmount() { + if (CharacterSkill.GetOwner(this) == null || this.skillsBase == null) { + Logger.error("owner or SkillsBase not found for skill " + this.getObjectUUID()); + this.baseAmount = 1; + this.modifiedAmount = 1; + return; + } + + //Get any rune bonus + float bonus = 0f; + if (CharacterSkill.GetOwner(this).getBonuses() != null) { + //Get bonuses from runes + bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType); + } + + //Get Base skill for modified stats + //TODO this fomula needs verified + float base = 7f; + float statMod = 0.5f; + if (this.skillsBase.getStrMod() > 0) + statMod += (float) this.skillsBase.getStrMod() * (float) CharacterSkill.GetOwner(this).getStatStrCurrent() / 100f; + if (this.skillsBase.getDexMod() > 0) + statMod += (float) this.skillsBase.getDexMod() * (float) CharacterSkill.GetOwner(this).getStatDexCurrent() / 100f; + if (this.skillsBase.getConMod() > 0) + statMod += (float) this.skillsBase.getConMod() * (float) CharacterSkill.GetOwner(this).getStatConCurrent() / 100f; + if (this.skillsBase.getIntMod() > 0) + statMod += (float) this.skillsBase.getIntMod() * (float) CharacterSkill.GetOwner(this).getStatIntCurrent() / 100f; + if (this.skillsBase.getSpiMod() > 0) + statMod += (float) this.skillsBase.getSpiMod() * (float) CharacterSkill.GetOwner(this).getStatSpiCurrent() / 100f; + if (statMod < 1) + statMod = 1f; + else if (statMod > 600) + statMod = 600f; + base += CharacterSkill.baseSkillValues[(int) statMod]; + SourceType sourceType = SourceType.GetSourceType(this.skillsBase.getNameNoSpace()); + + //Get any rune, effect and item bonus + + if (CharacterSkill.GetOwner(this).getBonuses() != null) { + //add bonuses from effects/items and runes + base += bonus + CharacterSkill.GetOwner(this).getBonuses().getFloat(ModType.Skill, sourceType); + } + + if (base < 1f) + this.baseAmount = 1f; + else + this.baseAmount = base; + + float modAmount = this.baseAmount + calculateAmountAfterTrains(); + + if (CharacterSkill.GetOwner(this).getBonuses() != null) { + //Multiply any percent bonuses + modAmount *= (1 + CharacterSkill.GetOwner(this).getBonuses().getFloatPercentAll(ModType.Skill, sourceType)); + } + + this.modifiedAmount = (int) (modAmount); + } + + /** + * @ Calculates Modified Skill Percentage + * Call this when number of trains change for skill + */ + public float calculateAmountAfterTrains() { + if (this.skillsBase == null) { + Logger.error("SkillsBase not found for skill " + this.getObjectUUID()); + this.modifiedAmount = this.baseAmount; + } + int amount; + + int trains = this.numTrains.get(); + if (trains < 10) + amount = (trains * 2); + else if (trains < 90) + amount = 10 + trains; + else if (trains < 134) + amount = 100 + ((trains - 90) / 2); + else + amount = 122 + ((trains - 134) / 3); + + return amount; + } + + public int getSkillPercentFromAttributes() { + AbstractCharacter ac = CharacterSkill.GetOwner(this); + + if (ac == null) + return 0; + + float statMod = 0; + + if (this.skillsBase.getStrMod() > 0) { + float strengthModPercent = (float) this.skillsBase.getStrMod() * .01f; + strengthModPercent *= ac.getStatStrCurrent() * .01f + .6f; + statMod += strengthModPercent; + } + + if (this.skillsBase.getDexMod() > 0) { + float dexModPercent = (float) this.skillsBase.getDexMod() * .01f; + dexModPercent *= ac.getStatDexCurrent() * .01f + .6f; + statMod += dexModPercent; + } + if (this.skillsBase.getConMod() > 0) { + float conModPercent = (float) this.skillsBase.getConMod() * .01f; + conModPercent *= ac.getStatConCurrent() * .01f + .6f; + statMod += conModPercent; + } + if (this.skillsBase.getIntMod() > 0) { + float intModPercent = (float) this.skillsBase.getIntMod() * .01f; + intModPercent *= ac.getStatIntCurrent() * .01f + .6f; + statMod += intModPercent; + } + if (this.skillsBase.getSpiMod() > 0) { + float spiModPercent = (float) this.skillsBase.getSpiMod() * .01f; + spiModPercent *= ac.getStatSpiCurrent() * .01f + .6f; + statMod += spiModPercent; + } + + statMod = (float) (Math.pow(statMod, 1.5f) * 15f); + if (statMod < 1) + statMod = 1f; + else if (statMod > 600) + statMod = 600f; + + + return (int) statMod; + } + + public int getSkillPercentFromTrains() { + + int trains = this.numTrains.get(); + if (trains <= 10) + return 2 * trains; + if (trains <= 90) + return trains + 10; + if (trains > 130) + return (int) (120 - ((trains - 130) * -0.33000001)); + return (int) (100 - ((trains - 90) * -0.5)); + + } + + public int getTotalSkillPercet() { + + AbstractCharacter ac = CharacterSkill.GetOwner(this); + + if (ac == null) + return 0; + + float bonus = 0f; + SourceType sourceType = SourceType.GetSourceType(this.skillsBase.getNameNoSpace()); + if (CharacterSkill.GetOwner(this).getBonuses() != null) { + //Get bonuses from runes + bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType); + } + return this.getSkillPercentFromTrains() + this.getSkillPercentFromAttributes(); + } + + @Override + public void updateDatabase() { + DbManager.CharacterSkillQueries.updateDatabase(this); + } + + public int getRequiredLevel() { + return requiredLevel; + } + + public void setRequiredLevel(int requiredLevel) { + this.requiredLevel = requiredLevel; + } +} diff --git a/src/engine/objects/CharacterTitle.java b/src/engine/objects/CharacterTitle.java new file mode 100644 index 00000000..796a7ef1 --- /dev/null +++ b/src/engine/objects/CharacterTitle.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.net.ByteBufferWriter; + +import java.nio.ByteBuffer; + +public enum CharacterTitle { + + NONE(0, 0, 0, ""), + CSR_1(255, 0, 0, "CCR"), + CSR_2(255, 0, 0, "CCR"), + CSR_3(255, 0, 0, "CCR"), + CSR_4(251, 181, 13, "CCR"), + DEVELOPER(166, 153, 114, "Programmer"), + QA(88, 250, 244, "GIRLFRIEND"); + + int headerLength, footerLength; + private ByteBuffer header; + private ByteBuffer footer; + + CharacterTitle(int _r, int _g, int _b, String _prefix) { + char[] str_header = ("^\\c" + + (((_r < 100) ? ((_r < 10) ? "00" : "0") : "") + ((byte) _r & 0xFF)) + + (((_g < 100) ? ((_g < 10) ? "00" : "0") : "") + ((byte) _g & 0xFF)) + + (((_b < 100) ? ((_b < 10) ? "00" : "0") : "") + ((byte) _b & 0xFF)) + + '<' + _prefix + "> ").toCharArray(); + + char[] str_footer = ("^\\c255255255").toCharArray(); + + this.headerLength = str_header.length; + this.footerLength = str_footer.length; + + this.header = ByteBuffer.allocateDirect(headerLength << 1); + this.footer = ByteBuffer.allocateDirect(footerLength << 1); + + ByteBufferWriter headWriter = new ByteBufferWriter(header); + + for (char c : str_header) { + headWriter.putChar(c); + } + + ByteBufferWriter footWriter = new ByteBufferWriter(footer); + + for (char c : str_footer) { + footWriter.putChar(c); + } + } + + public void _serializeFirstName(ByteBufferWriter writer, String firstName) { + _serializeFirstName(writer, firstName, false); + } + + public void _serializeFirstName(ByteBufferWriter writer, String firstName, boolean smallString) { + if (this.ordinal() == 0) { + if (smallString) + writer.putSmallString(firstName); + else + writer.putString(firstName); + return; + } + + char[] chars = firstName.toCharArray(); + + if (smallString) + writer.put((byte) (chars.length + this.headerLength)); + else + writer.putInt(chars.length + this.headerLength); + writer.putBB(header); + + for (char c : chars) { + writer.putChar(c); + } + } + + public void _serializeLastName(ByteBufferWriter writer, String lastName, boolean haln, boolean asciiLastName) { + _serializeLastName(writer, lastName, haln, asciiLastName, false); + } + + public void _serializeLastName(ByteBufferWriter writer, String lastName, boolean haln, boolean asciiLastName, boolean smallString) { + if (!haln || asciiLastName) { + if (this.ordinal() == 0) { + if (smallString) + writer.putSmallString(lastName); + else + writer.putString(lastName); + return; + } + } + + if (!haln || asciiLastName) { + char[] chars = lastName.toCharArray(); + + if (smallString) + writer.put((byte) (chars.length + this.footerLength)); + else + writer.putInt(chars.length + this.footerLength); + + for (char c : chars) { + writer.putChar(c); + } + + writer.putBB(footer); + } else { + if (smallString) + writer.put((byte) this.footerLength); + else + writer.putInt(this.footerLength); + writer.putBB(footer); + } + + } +} diff --git a/src/engine/objects/City.java b/src/engine/objects/City.java new file mode 100644 index 00000000..d3d36eac --- /dev/null +++ b/src/engine/objects/City.java @@ -0,0 +1,1444 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.*; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.RealmMap; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.CityRecord; +import engine.db.archive.DataWarehouse; +import engine.gameManager.*; +import engine.math.Bounds; +import engine.math.FastMath; +import engine.math.Vector2f; +import engine.math.Vector3fImmutable; +import engine.net.ByteBufferWriter; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.TaxResourcesMsg; +import engine.net.client.msg.ViewResourcesMessage; +import engine.powers.EffectsBase; +import engine.server.MBServerStatics; +import engine.server.world.WorldServer; +import engine.workthreads.DestroyCityThread; +import engine.workthreads.TransferCityThread; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class City extends AbstractWorldObject { + + public static long lastCityUpdate = 0; + public final HashSet _playerMemory = new HashSet<>(); + public java.time.LocalDateTime established; + public boolean hasBeenTransfered = false; + public LocalDateTime realmTaxDate; + public ReentrantReadWriteLock transactionLock = new ReentrantReadWriteLock(); + public volatile boolean protectionEnforced = true; + public ArrayList cityBarracks; + public ArrayList cityOutlaws = new ArrayList<>(); + protected Zone parentZone; + private String cityName; + private String motto; + private String description; + private int isNoobIsle; //1: noob, 0: not noob: -1: not noob, no teleport + private int population = 0; + private int siegesWithstood = 0; + private int realmID; + private int radiusType; + private float bindRadius; + private float statLat; + private float statAlt; + private float statLon; + private float bindX; + private float bindZ; + private byte isNpc; //aka Safehold + private byte isCapital = 0; + private byte isSafeHold; + private boolean forceRename = false; + private boolean noTeleport = false; //used by npc cities + private boolean noRepledge = false; //used by npc cities + private boolean isOpen = false; + private int treeOfLifeID; + private Vector3fImmutable location = Vector3fImmutable.ZERO; + + // Players who have entered the city (used for adding and removing affects) + private Vector3fImmutable bindLoc; + private int warehouseBuildingID = 0; + private boolean open = false; + private boolean reverseKOS = false; + private String hash; + + /** + * ResultSet Constructor + */ + + public City(ResultSet rs) throws SQLException { + super(rs); + try { + this.cityName = rs.getString("name"); + this.motto = rs.getString("motto"); + this.isNpc = rs.getByte("isNpc"); + this.isSafeHold = (byte) ((this.isNpc == 1) ? 1 : 0); + this.description = ""; // TODO Implement this! + this.isNoobIsle = rs.getByte("isNoobIsle"); // Noob + this.gridObjectType = GridObjectType.STATIC; + // Island + // City(00000001), + // Otherwise(FFFFFFFF) + this.population = rs.getInt("population"); + this.siegesWithstood = rs.getInt("siegesWithstood"); + + java.sql.Timestamp establishedTimeStamp = rs.getTimestamp("established"); + + if (establishedTimeStamp != null) + this.established = java.time.LocalDateTime.ofInstant(establishedTimeStamp.toInstant(), ZoneId.systemDefault()); + + this.location = new Vector3fImmutable(rs.getFloat("xCoord"), rs.getFloat("yCoord"), rs.getFloat("zCoord")); + this.statLat = rs.getFloat("xCoord"); + this.statAlt = rs.getFloat("yCoord"); + this.statLon = rs.getFloat("zCoord"); + + java.sql.Timestamp realmTaxTimeStamp = rs.getTimestamp("realmTaxDate"); + + if (realmTaxTimeStamp != null) + this.realmTaxDate = realmTaxTimeStamp.toLocalDateTime(); + + if (this.realmTaxDate == null) + this.realmTaxDate = LocalDateTime.now(); + + this.treeOfLifeID = rs.getInt("treeOfLifeUUID"); + this.bindX = rs.getFloat("bindX"); + this.bindZ = rs.getFloat("bindZ"); + this.bindLoc = new Vector3fImmutable(this.location.getX() + this.bindX, + this.location.getY(), + this.location.getZ() + this.bindZ); + this.radiusType = rs.getInt("radiusType"); + float bindradiustemp = rs.getFloat("bindRadius"); + if (bindradiustemp > 2) + bindradiustemp -= 2; + + this.bindRadius = bindradiustemp; + + this.forceRename = rs.getInt("forceRename") == 1; + this.open = rs.getInt("open") == 1; + + if (this.cityName.equals("Perdition") || this.cityName.equals("Bastion")) { + this.noTeleport = true; + this.noRepledge = true; + } else { + this.noTeleport = false; + this.noRepledge = false; + } + + this.hash = rs.getString("hash"); + + if (this.motto.isEmpty()) { + Guild guild = this.getGuild(); + + if (guild != null && guild.isEmptyGuild() == false) + this.motto = guild.getMotto(); + } + + + //Disabled till i finish. + // this.reverseKOS = rs.getInt("kos") == 1; + + + Zone zone = ZoneManager.getZoneByUUID(rs.getInt("parent")); + + if (zone != null) + setParent(zone); + + //npc cities without heightmaps except swampstone are specials. + + + this.realmID = rs.getInt("realmID"); + + } catch (Exception e) { + Logger.error(e); + } + + // *** Refactor: Is this working? Intended to supress + // login server errors from attempting to + // load cities/realms along with players + + + } + + /* + * Utils + */ + + public static void _serializeForClientMsg(City city, ByteBufferWriter writer) { + City.serializeForClientMsg(city, writer); + } + + public static void serializeForClientMsg(City city, ByteBufferWriter writer) { + AbstractCharacter guildRuler; + Guild rulingGuild; + Guild rulingNation; + java.time.LocalDateTime dateTime1900; + + // Cities aren't a city without a TOL. Time to early exit. + // No need to spam the log here as non-existant TOL's are indicated + // during bootstrap routines. + + if (city.getTOL() == null) { + + Logger.error("NULL TOL FOR " + city.cityName); + } + + + // Assign city owner + + if (city.getTOL() != null) + guildRuler = city.getTOL().getOwner(); + else + guildRuler = null; + + // If is an errant tree, use errant guild for serialization. + // otherwise we serialize the soverign guild + + if (guildRuler == null) + rulingGuild = Guild.getErrantGuild(); + else + rulingGuild = guildRuler.getGuild(); + + rulingNation = rulingGuild.getNation(); + + // Begin Serialzing soverign guild data + writer.putInt(city.getObjectType().ordinal()); + writer.putInt(city.getObjectUUID()); + writer.putString(city.cityName); + writer.putInt(rulingGuild.getObjectType().ordinal()); + writer.putInt(rulingGuild.getObjectUUID()); + + writer.putString(rulingGuild.getName()); + writer.putString(city.motto); + writer.putString(rulingGuild.getLeadershipType()); + + // Serialize guild ruler's name + // If tree is abandoned blank out the name + // to allow them a rename. + + if (guildRuler == null) + writer.putString(""); + else + writer.putString(guildRuler.getFirstName() + ' ' + guildRuler.getLastName()); + + writer.putInt(rulingGuild.getCharter()); + writer.putInt(0); // always 00000000 + + writer.put(city.isSafeHold); + + writer.put((byte) 1); + writer.put((byte) 1); // *** Refactor: What are these flags? + writer.put((byte) 1); + writer.put((byte) 1); + writer.put((byte) 1); + + GuildTag._serializeForDisplay(rulingGuild.getGuildTag(), writer); + GuildTag._serializeForDisplay(rulingNation.getGuildTag(), writer); + + writer.putInt(0);// TODO Implement description text + + writer.put((byte) 1); + + if (city.isCapital > 0) + writer.put((byte) 1); + else + writer.put((byte) 0); + + writer.put((byte) 1); + + // Begin serializing nation guild info + + if (rulingNation.isEmptyGuild()) { + writer.putInt(rulingGuild.getObjectType().ordinal()); + writer.putInt(rulingGuild.getObjectUUID()); + } else { + writer.putInt(rulingNation.getObjectType().ordinal()); + writer.putInt(rulingNation.getObjectUUID()); + } + + + // Serialize nation name + + if (rulingNation.isEmptyGuild()) + writer.putString("None"); + else + writer.putString(rulingNation.getName()); + + writer.putInt(city.getTOL().getRank()); + + if (city.isNoobIsle > 0) + writer.putInt(1); + else + writer.putInt(0xFFFFFFFF); + + writer.putInt(city.population); + + if (rulingNation.isEmptyGuild()) + writer.putString(" "); + else + writer.putString(Guild.GetGL(rulingNation).getFirstName() + ' ' + Guild.GetGL(rulingNation).getLastName()); + + + writer.putLocalDateTime(city.established); + +// writer.put((byte) city.established.getDayOfMonth()); +// writer.put((byte) city.established.minusMonths(1).getMonth().getValue()); +// writer.putInt((int) years); +// writer.put((byte) hours); +// writer.put((byte) minutes); +// writer.put((byte) seconds); + + writer.putFloat(city.location.x); + writer.putFloat(city.location.y); + writer.putFloat(city.location.z); + + writer.putInt(city.siegesWithstood); + + writer.put((byte) 1); + writer.put((byte) 0); + writer.putInt(0x64); + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + } + + public static Vector3fImmutable getBindLoc(int cityID) { + + City city; + + city = City.getCity(cityID); + + if (city == null) + return Enum.Ruins.getRandomRuin().getLocation(); + + return city.getBindLoc(); + } + + public static ArrayList getCitiesToTeleportTo(PlayerCharacter pc) { + + ArrayList cities = new ArrayList<>(); + + if (pc == null) + return cities; + + Guild pcG = pc.getGuild(); + + ConcurrentHashMap worldCities = DbManager.getMap(Enum.GameObjectType.City); + + //add npc cities + for (AbstractGameObject ago : worldCities.values()) { + + if (ago.getObjectType().equals(GameObjectType.City)) { + City city = (City) ago; + + if (city.noTeleport) + continue; + + if (city.parentZone != null && city.parentZone.isPlayerCity()) { + + if (pc.getAccount().status.equals(AccountStatus.ADMIN)) { + cities.add(city); + } else + //list Player cities + + //open city, just list + if (city.open && city.getTOL() != null && city.getTOL().getRank() > 4) { + + if (!BuildingManager.IsPlayerHostile(city.getTOL(), pc)) + cities.add(city); //verify nation or guild is same + } else if (Guild.sameNationExcludeErrant(city.getGuild(), pcG)) + cities.add(city); + + + } else if (city.isNpc == 1) { + //list NPC cities + Guild g = city.getGuild(); + if (g == null) { + if (city.isNpc == 1) + if (city.isNoobIsle == 1) { + if (pc.getLevel() < 21) + cities.add(city); + } else if (pc.getLevel() > 9) + cities.add(city); + + } else if (pc.getLevel() >= g.getTeleportMin() && pc.getLevel() <= g.getTeleportMax()) { + + + cities.add(city); + } + } + + + } + } + + return cities; + } + + public static ArrayList getCitiesToRepledgeTo(PlayerCharacter pc) { + ArrayList cities = new ArrayList<>(); + if (pc == null) + return cities; + Guild pcG = pc.getGuild(); + + ConcurrentHashMap worldCities = DbManager.getMap(Enum.GameObjectType.City); + + //add npc cities + for (AbstractGameObject ago : worldCities.values()) { + if (ago.getObjectType().equals(GameObjectType.City)) { + City city = (City) ago; + if (city.noRepledge) + continue; + + if (city.parentZone != null && city.parentZone.isPlayerCity()) { + + //list Player cities + //open city, just list + if (pc.getAccount().status.equals(AccountStatus.ADMIN)) { + cities.add(city); + } else if (city.open && city.getTOL() != null && city.getTOL().getRank() > 4) { + + if (!BuildingManager.IsPlayerHostile(city.getTOL(), pc)) + cities.add(city); //verify nation or guild is same + } else if (Guild.sameNationExcludeErrant(city.getGuild(), pcG)) + cities.add(city); + + } else if (city.isNpc == 1) { + //list NPC cities + + Guild g = city.getGuild(); + if (g == null) { + if (city.isNpc == 1) + if (city.isNoobIsle == 1) { + if (pc.getLevel() < 21) + cities.add(city); + } else if (pc.getLevel() > 9) + cities.add(city); + } else if (pc.getLevel() >= g.getRepledgeMin() && pc.getLevel() <= g.getRepledgeMax()) { + + cities.add(city); + } + } + } + } + return cities; + } + + public static City getCity(int cityId) { + + if (cityId == 0) + return null; + + City city = (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId); + if (city != null) + return city; + + return DbManager.CityQueries.GET_CITY(cityId); + + } + + public static City GetCityFromCache(int cityId) { + + if (cityId == 0) + return null; + + return (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId); + } + + public boolean renameCity(String cityName) { + if (!DbManager.CityQueries.renameCity(this, cityName)) + return false; + if (!DbManager.CityQueries.updateforceRename(this, false)) + return false; + + this.cityName = cityName; + this.forceRename = false; + return true; + } + + public boolean updateTOL(Building tol) { + if (tol == null) + return false; + if (!DbManager.CityQueries.updateTOL(this, tol.getObjectUUID())) + return false; + this.treeOfLifeID = tol.getObjectUUID(); + return true; + } + + public boolean renameCityForNewPlant(String cityName) { + if (!DbManager.CityQueries.renameCity(this, cityName)) + return false; + if (!DbManager.CityQueries.updateforceRename(this, true)) + return false; + + this.cityName = cityName; + this.forceRename = true; + return true; + } + + public String getCityName() { + + return cityName; + } + + public String getMotto() { + return motto; + } + + public String getDescription() { + return description; + } + + public Building getTOL() { + if (this.treeOfLifeID == 0) + return null; + + return BuildingManager.getBuildingFromCache(this.treeOfLifeID); + + } + + public int getIsNoobIsle() { + return isNoobIsle; + } + + public int getPopulation() { + return population; + } + + /** + * @param population the population to set + */ + public void setPopulation(int population) { + this.population = population; + } + + public int getSiegesWithstood() { + return siegesWithstood; + } + + /** + * @param siegesWithstood the siegesWithstood to set + */ + public void setSiegesWithstood(int siegesWithstood) { + + // early exit if setting to current value + + if (this.siegesWithstood == siegesWithstood) + return; + + if (DbManager.CityQueries.updateSiegesWithstood(this, siegesWithstood) == true) + this.siegesWithstood = siegesWithstood; + else + Logger.error("Error when writing to database for cityUUID: " + this.getObjectUUID()); + } + + public float getLatitude() { + return this.location.x; + } + + public float getLongitude() { + return this.location.z; + } + + public float getAltitude() { + return this.location.y; + } + + @Override + public Vector3fImmutable getLoc() { + return this.location; + } + + public byte getIsNpcOwned() { + return isNpc; + } + + public byte getIsSafeHold() { + return this.isSafeHold; + } + + public boolean isSafeHold() { + return (this.isSafeHold == (byte) 1); + } + + public byte getIsCapital() { + return isCapital; + } + + public void setIsCapital(boolean state) { + this.isCapital = (state) ? (byte) 1 : (byte) 0; + } + + public int getRadiusType() { + return this.radiusType; + } + + public float getBindRadius() { + return this.bindRadius; + } + + public int getRank() { + return (this.getTOL() == null) ? 0 : this.getTOL().getRank(); + } + + /* + * Serializing + */ + + public Bane getBane() { + return Bane.getBane(this.getObjectUUID()); + } + + public Zone getParent() { + return this.parentZone; + } + + public void setParent(Zone zone) { + + try { + + + this.parentZone = zone; + this.location = new Vector3fImmutable(zone.absX + statLat, zone.absY + statAlt, zone.absZ + statLon); + this.bindLoc = new Vector3fImmutable(this.location.x + this.bindX, + this.location.y, + this.location.z + this.bindZ); + + // set city bounds + + Bounds cityBounds = Bounds.borrow(); + cityBounds.setBounds(new Vector2f(this.location.x + 64, this.location.z + 64), // location x and z are offset by 64 from the center of the city. + new Vector2f(Enum.CityBoundsType.GRID.extents, Enum.CityBoundsType.GRID.extents), + 0.0f); + this.setBounds(cityBounds); + + if (zone.getHeightMap() == null && this.isNpc == 1 && this.getObjectUUID() != 1213) { + HeightMap.GenerateCustomHeightMap(zone); + Logger.info(zone.getName() + " created custom heightmap"); + } + } catch (Exception e) { + Logger.error(e); + } + } + + public boolean isCityZone(Zone zone) { + + if (zone == null || this.parentZone == null) + return false; + + return zone.getObjectUUID() == this.parentZone.getObjectUUID(); + + } + + public AbstractCharacter getOwner() { + + if (this.getTOL() == null) + return null; + + int ownerID = this.getTOL().getOwnerUUID(); + + if (ownerID == 0) + return null; + + if (this.isNpc == 1) + return NPC.getNPC(ownerID); + else + return PlayerCharacter.getPlayerCharacter(ownerID); + } + + public Guild getGuild() { + + if (this.getTOL() == null) + return null; + + + if (this.isNpc == 1) { + + if (this.getTOL().getOwner() == null) + return null; + return this.getTOL().getOwner().getGuild(); + } else { + if (this.getTOL().getOwner() == null) + return null; + return this.getTOL().getOwner().getGuild(); + } + } + + public boolean openCity(boolean open) { + if (!DbManager.CityQueries.updateOpenCity(this, open)) + return false; + this.open = open; + return true; + } + + public Vector3fImmutable getBindLoc() { + Vector3fImmutable treeLoc = null; + + if (this.getTOL() != null && this.getTOL().getRank() == 8) + treeLoc = this.getTOL().getStuckLocation(); + + if (treeLoc != null) + return treeLoc; + + if (this.radiusType == 1 && this.bindRadius > 0f) { + //square radius + float x = this.bindLoc.getX(); + float z = this.bindLoc.getZ(); + float offset = ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius; + int direction = ThreadLocalRandom.current().nextInt(4); + + switch (direction) { + case 0: + x += this.bindRadius; + z += offset; + break; + case 1: + x += offset; + z -= this.bindRadius; + break; + case 2: + x -= this.bindRadius; + z += offset; + break; + case 3: + x += offset; + z += this.bindRadius; + break; + } + return new Vector3fImmutable(x, this.bindLoc.getY(), z); + } else if (this.radiusType == 2 && this.bindRadius > 0f) { + //circle radius + Vector3fImmutable dir = FastMath.randomVector2D(); + return this.bindLoc.scaleAdd(this.bindRadius, dir); + } else if (this.radiusType == 3 && this.bindRadius > 0f) { + //random inside square + float x = this.bindLoc.getX(); + x += ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius; + float z = this.bindLoc.getZ(); + z += ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius; + return new Vector3fImmutable(x, this.bindLoc.getY(), z); + } else if (this.radiusType == 4 && this.bindRadius > 0f) { + //random inside circle + Vector3fImmutable dir = FastMath.randomVector2D(); + return this.bindLoc.scaleAdd(ThreadLocalRandom.current().nextFloat() * this.bindRadius, dir); + } else + //spawn at bindLoc + //System.out.println("x: " + this.bindLoc.x + ", z: " + this.bindLoc.z); + return this.bindLoc; + } + + public NPC getRuneMaster() { + NPC outNPC = null; + + if (this.getTOL() == null) + return outNPC; + + for (AbstractCharacter npc : getTOL().getHirelings().keySet()) { + if (npc.getObjectType() == GameObjectType.NPC) + if (((NPC) npc).getContract().isRuneMaster() == true) + outNPC = (NPC) npc; + } + + return outNPC; + } + + public boolean isOpen() { + return open; + } + + @Override + public void updateDatabase() { + // TODO Create update logic. + } + + @Override + public void runAfterLoad() { + + // Set city bounds + // *** Note: Moved to SetParent() + // for some undocumented reason + + // Set city motto to current guild motto + + if (BuildingManager.getBuilding(this.treeOfLifeID) == null) + Logger.info("City UID " + this.getObjectUUID() + " Failed to Load Tree of Life with ID " + this.treeOfLifeID); + + if ((ConfigManager.serverType.equals(ServerType.WORLDSERVER)) + && (this.isNpc == (byte) 0)) { + + Realm wsr = Realm.getRealm(this.realmID); + + if (wsr != null) + wsr.addCity(this.getObjectUUID()); + else + Logger.error("Unable to find realm of ID " + realmID + " for city " + this.getObjectUUID()); + } + + if (this.getGuild() != null) { + this.motto = this.getGuild().getMotto(); + + // Determine if this city is a nation capitol + + if (this.getGuild().getGuildState() == GuildState.Nation) + for (Guild sub : this.getGuild().getSubGuildList()) { + + if ((sub.getGuildState() == GuildState.Protectorate) || + (sub.getGuildState() == GuildState.Province)) + this.isCapital = 1; + } + + ArrayList guildList = Guild.GuildRoster(this.getGuild()); + + this.population = guildList.size(); + } + + // Banes are loaded for this city from the database at this point + + if (this.getBane() == null) + return; + + // if this city is baned, add the siege effect + + try { + this.getTOL().addEffectBit((1 << 16)); + this.getBane().getStone().addEffectBit((1 << 19)); + ; + } catch (Exception e) { + Logger.info("Failed ao add bane effects on city." + e.getMessage()); + } + } + + public void addCityEffect(EffectsBase effectBase, int rank) { + + HashSet currentPlayers; + PlayerCharacter player; + + // Add this new effect to the current city effect collection. + // so any new player to the grid will have all effects applied + + this.addEffectNoTimer(Integer.toString(effectBase.getUUID()), effectBase, rank, false); + + // Any players currently in the zone will not be processed by the heartbeat + // if it's not the first effect toggled so we do it here manually + + currentPlayers = WorldGrid.getObjectsInRangePartial(this.location, this.parentZone.getBounds().getHalfExtents().x * 1.2f, MBServerStatics.MASK_PLAYER); + + for (AbstractWorldObject playerObject : currentPlayers) { + + if (playerObject == null) + continue; + if (!this.isLocationWithinSiegeBounds(playerObject.getLoc())) + continue; + + player = (PlayerCharacter) playerObject; + player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, rank, MBServerStatics.FOURTYFIVE_SECONDS, true, this); + } + + } + + public void removeCityEffect(EffectsBase effectBase, int rank, boolean refreshEffect) { + + + PlayerCharacter player; + + // Remove the city effect from the ago's internal collection + + if (this.getEffects().containsKey(Integer.toString(effectBase.getUUID()))) + this.getEffects().remove(Integer.toString(effectBase.getUUID())); + + // Any players currently in the zone will not be processed by the heartbeat + // so we do it here manually + + + for (Integer playerID : this._playerMemory) { + + player = PlayerCharacter.getFromCache(playerID); + if (player == null) + continue; + + player.endEffectNoPower(Integer.toString(effectBase.getUUID())); + + // Reapply effect with timeout? + + if (refreshEffect == true) + player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, rank, MBServerStatics.FOURTYFIVE_SECONDS, false, this); + + } + + } + + public Warehouse getWarehouse() { + if (this.warehouseBuildingID == 0) + return null; + return Warehouse.warehouseByBuildingUUID.get(this.warehouseBuildingID); + } + + public Realm getRealm() { + + return Realm.getRealm(this.realmID); + + } + + public boolean isLocationOnCityGrid(Vector3fImmutable insideLoc) { + + Bounds newBounds = Bounds.borrow(); + newBounds.setBounds(insideLoc); + boolean collided = Bounds.collide(this.getBounds(), newBounds, 0); + newBounds.release(); + return collided; + } + + public boolean isLocationOnCityGrid(Bounds newBounds) { + + boolean collided = Bounds.collide(this.getBounds(), newBounds, 0); + return collided; + } + + public boolean isLocationWithinSiegeBounds(Vector3fImmutable insideLoc) { + + return insideLoc.isInsideCircle(this.getLoc(), CityBoundsType.ZONE.extents); + + } + + public boolean isLocationOnCityZone(Vector3fImmutable insideLoc) { + return Bounds.collide(insideLoc, this.parentZone.getBounds()); + } + + private void applyAllCityEffects(PlayerCharacter player) { + + Effect effect; + EffectsBase effectBase; + + try { + for (String cityEffect : this.getEffects().keySet()) { + + effect = this.getEffects().get(cityEffect); + effectBase = effect.getEffectsBase(); + + if (effectBase == null) + continue; + + player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, effect.getTrains(), MBServerStatics.FOURTYFIVE_SECONDS, true, this); + } + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + } + + private void removeAllCityEffects(PlayerCharacter player, boolean force) { + + Effect effect; + EffectsBase effectBase; + + try { + for (String cityEffect : this.getEffects().keySet()) { + + effect = this.getEffects().get(cityEffect); + effectBase = effect.getEffectsBase(); + + if (player.getEffects().get(cityEffect) == null) + return; + + // player.endEffectNoPower(cityEffect); + player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, effect.getTrains(), MBServerStatics.FOURTYFIVE_SECONDS, false, this); + } + } catch (Exception e) { + Logger.error(e.getMessage()); + } + } + + /* All characters in city player memory but + * not the current memory have obviously + * left the city. Remove their affects. + */ + + public void onEnter() { + + HashSet currentPlayers; + HashSet currentMemory; + PlayerCharacter player; + + // Gather current list of players within the zone bounds + + currentPlayers = WorldGrid.getObjectsInRangePartial(this.location, CityBoundsType.ZONE.extents, MBServerStatics.MASK_PLAYER); + currentMemory = new HashSet<>(); + + for (AbstractWorldObject playerObject : currentPlayers) { + + if (playerObject == null) + continue; + + player = (PlayerCharacter) playerObject; + currentMemory.add(player.getObjectUUID()); + + // Player is already in our memory + + if (_playerMemory.contains(player.getObjectUUID())) + continue; + + if (!this.isLocationWithinSiegeBounds(player.getLoc())) + continue; + // Apply safehold affect to player if needed + + if ((this.isSafeHold == 1)) + player.setSafeZone(true); + + //add spire effects. + if (this.getEffects().size() > 0) + this.applyAllCityEffects(player); + + // Add player to our city's memory + + _playerMemory.add(player.getObjectUUID()); + + // ***For debugging + // Logger.info("PlayerMemory for ", this.getCityName() + ": " + _playerMemory.size()); + } + try { + onExit(currentMemory); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + } + + private void onExit(HashSet currentMemory) { + + PlayerCharacter player; + int playerUUID = 0; + HashSet toRemove = new HashSet<>(); + Iterator iter = _playerMemory.iterator(); + + while (iter.hasNext()) { + + playerUUID = iter.next(); + + + player = PlayerCharacter.getFromCache(playerUUID); + + if (this.isLocationWithinSiegeBounds(player.getLoc())) + continue; + + // Remove players safezone status if warranted + // they can assumed to be not on the citygrid at + // this point. + + + player.setSafeZone(false); + + this.removeAllCityEffects(player, false); + + // We will remove this player after iteration is complete + // so store it in a temporary collection + + toRemove.add(playerUUID); + // ***For debugging + // Logger.info("PlayerMemory for ", this.getCityName() + ": " + _playerMemory.size()); + } + + // Remove players from city memory + + _playerMemory.removeAll(toRemove); + for (Integer removalUUID : toRemove) { + if (this.cityOutlaws.contains(removalUUID)) + this.cityOutlaws.remove(removalUUID); + } + } + + public int getWarehouseBuildingID() { + return warehouseBuildingID; + } + + public void setWarehouseBuildingID(int warehouseBuildingID) { + this.warehouseBuildingID = warehouseBuildingID; + } + + public final void destroy() { + + Thread destroyCityThread = new Thread(new DestroyCityThread(this)); + + destroyCityThread.setName("deestroyCity:" + this.getName()); + destroyCityThread.start(); + } + + public final void transfer(AbstractCharacter newOwner) { + + Thread transferCityThread = new Thread(new TransferCityThread(this, newOwner)); + + transferCityThread.setName("TransferCity:" + this.getName()); + transferCityThread.start(); + } + + public final void claim(AbstractCharacter sourcePlayer) { + + Guild sourceNation; + Guild sourceGuild; + Zone cityZone; + + sourceGuild = sourcePlayer.getGuild(); + + if (sourceGuild == null) + return; + + sourceNation = sourcePlayer.getGuild().getNation(); + + if (sourceGuild.isEmptyGuild()) + return; + + //cant claim tree with owned tree. + + if (sourceGuild.getOwnedCity() != null) + return; + + cityZone = this.parentZone; + + // Can't claim a tree not in a player city zone + + // Reset sieges withstood + + this.setSiegesWithstood(0); + + this.hasBeenTransfered = true; + + // If currently a sub of another guild, desub when + // claiming your new tree and set as Landed + + if (!sourceNation.isEmptyGuild() && sourceNation != sourceGuild) { + if (!DbManager.GuildQueries.UPDATE_PARENT(sourceGuild.getObjectUUID(), WorldServer.worldUUID)) { + ChatManager.chatGuildError((PlayerCharacter) sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return; + } + + sourceNation.getSubGuildList().remove(sourceGuild); + + if (sourceNation.getSubGuildList().isEmpty()) + sourceNation.downgradeGuildState(); + } + + // Link the mew guild with the tree + + if (!DbManager.GuildQueries.SET_GUILD_OWNED_CITY(sourceGuild.getObjectUUID(), this.getObjectUUID())) { + ChatManager.chatGuildError((PlayerCharacter) sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + return; + } + + sourceGuild.setCityUUID(this.getObjectUUID()); + + sourceGuild.setNation(sourceGuild); + sourceGuild.setGuildState(GuildState.Sovereign); + GuildManager.updateAllGuildTags(sourceGuild); + GuildManager.updateAllGuildBinds(sourceGuild, this); + + // Build list of buildings within this parent zone + + for (Building cityBuilding : cityZone.zoneBuildingSet) { + + // Buildings without blueprints are unclaimable + + if (cityBuilding.getBlueprintUUID() == 0) + continue; + + // All protection contracts are void upon transfer of a city + + // All protection contracts are void upon transfer of a city + //Dont forget to not Flip protection on Banestones and siege Equipment... Noob. + if (cityBuilding.getBlueprint() != null && !cityBuilding.getBlueprint().isSiegeEquip() + && cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE) + cityBuilding.setProtectionState(ProtectionState.NONE); + + // Transfer ownership of valid city assets + // these assets are autoprotected. + + if ((cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) + || (cityBuilding.getBlueprint().isWallPiece()) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) { + + cityBuilding.claim(sourcePlayer); + cityBuilding.setProtectionState(ProtectionState.PROTECTED); + } + } + + this.setForceRename(true); + + // Reset city timer for map update + + City.lastCityUpdate = System.currentTimeMillis(); + } + + public final boolean transferGuildLeader(PlayerCharacter sourcePlayer) { + + Guild sourceGuild; + Zone cityZone; + sourceGuild = sourcePlayer.getGuild(); + + + if (sourceGuild == null) + return false; + + if (sourceGuild.isEmptyGuild()) + return false; + + cityZone = this.parentZone; + + for (Building cityBuilding : cityZone.zoneBuildingSet) { + + // Buildings without blueprints are unclaimable + + if (cityBuilding.getBlueprintUUID() == 0) + continue; + + // All protection contracts are void upon transfer of a city + //Dont forget to not Flip protection on Banestones and siege Equipment... Noob. + + // Transfer ownership of valid city assets + // these assets are autoprotected. + + if ((cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) + || (cityBuilding.getBlueprint().isWallPiece()) + || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) + ) { + + cityBuilding.claim(sourcePlayer); + cityBuilding.setProtectionState(ProtectionState.PROTECTED); + } else if (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.WAREHOUSE) + cityBuilding.claim(sourcePlayer); + + + } + this.setForceRename(true); + CityRecord cityRecord = CityRecord.borrow(this, Enum.RecordEventType.TRANSFER); + DataWarehouse.pushToWarehouse(cityRecord); + return true; + + } + + /** + * @return the forceRename + */ + public boolean isForceRename() { + return forceRename; + } + + public void setForceRename(boolean forceRename) { + if (!DbManager.CityQueries.updateforceRename(this, forceRename)) + return; + this.forceRename = forceRename; + } + + public boolean isReverseKOS() { + return reverseKOS; + } + + public void setReverseKOS(boolean reverseKOS) { + this.reverseKOS = reverseKOS; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public void setHash() { + + this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID()); + + // Write hash to player character table + + DataWarehouse.writeHash(Enum.DataRecordType.CITY, this.getObjectUUID()); + } + + public boolean setRealmTaxDate(LocalDateTime realmTaxDate) { + + if (!DbManager.CityQueries.updateRealmTaxDate(this, realmTaxDate)) + return false; + + this.realmTaxDate = realmTaxDate; + return true; + + } + + //TODO use this for taxing later. +// public boolean isAfterTaxPeriod(LocalDateTime dateTime,PlayerCharacter player){ +// if (dateTime.isBefore(realmTaxDate)){ +// String wait = ""; +// float hours = 1000*60*60; +// float seconds = 1000; +// float hoursUntil = realmTaxDate.minus(dateTime.get).getMillis() /hours; +// int secondsUntil = (int) (realmTaxDate.minus(dateTime.getMillis()).getMillis() /seconds); +// if (hoursUntil < 1) +// wait = "You must wait " + secondsUntil + " seconds before taxing this city again!"; +// else +// wait = "You must wait " + hoursUntil + " hours before taxing this city again!"; +// ErrorPopupMsg.sendErrorMsg(player, wait); +// return false; +// } +// +// return true; +// } + + + public synchronized boolean TaxWarehouse(TaxResourcesMsg msg, PlayerCharacter player) { + + // Member variable declaration + Building building = BuildingManager.getBuildingFromCache(msg.getBuildingID()); + Guild playerGuild = player.getGuild(); + + if (building == null) { + ErrorPopupMsg.sendErrorMsg(player, "Not a valid Building!"); + return true; + } + + City city = building.getCity(); + if (city == null) { + ErrorPopupMsg.sendErrorMsg(player, "This building does not belong to a city."); + return true; + } + + + if (playerGuild == null || playerGuild.isEmptyGuild()) { + ErrorPopupMsg.sendErrorMsg(player, "You must belong to a guild to do that!"); + return true; + } + + if (playerGuild.getOwnedCity() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Your Guild needs to own a city!"); + return true; + } + + if (playerGuild.getOwnedCity().getTOL() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot find Tree of Life for your city!"); + return true; + } + + if (playerGuild.getOwnedCity().getTOL().getRank() != 8) { + ErrorPopupMsg.sendErrorMsg(player, "Your City needs to Own a realm!"); + return true; + } + + if (playerGuild.getOwnedCity().getRealm() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for your city!"); + return true; + } + Realm targetRealm = RealmMap.getRealmForCity(city); + + if (targetRealm == null) { + ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for city you are attempting to tax!"); + return true; + } + + if (targetRealm.getRulingCity() == null) { + ErrorPopupMsg.sendErrorMsg(player, "Realm Does not have a ruling city!"); + return true; + } + + if (targetRealm.getRulingCity().getObjectUUID() != playerGuild.getOwnedCity().getObjectUUID()) { + ErrorPopupMsg.sendErrorMsg(player, "Your guild does not rule this realm!"); + return true; + } + + if (playerGuild.getOwnedCity().getObjectUUID() == city.getObjectUUID()) { + ErrorPopupMsg.sendErrorMsg(player, "You cannot tax your own city!"); + return true; + } + + + if (!GuildStatusController.isTaxCollector(player.getGuildStatus())) { + ErrorPopupMsg.sendErrorMsg(player, "You Must be a tax Collector!"); + return true; + } + + + if (this.realmTaxDate.isAfter(LocalDateTime.now())) + return true; + if (msg.getResources().size() == 0) + return true; + + if (city.getWarehouse() == null) + return true; + Warehouse ruledWarehouse = playerGuild.getOwnedCity().getWarehouse(); + if (ruledWarehouse == null) + return true; + + + ItemBase.getItemHashIDMap(); + + ArrayList resources = new ArrayList<>(); + + float taxPercent = msg.getTaxPercent(); + if (taxPercent > 20) + taxPercent = .20f; + + for (int resourceHash : msg.getResources().keySet()) { + if (ItemBase.getItemHashIDMap().get(resourceHash) != null) + resources.add(ItemBase.getItemHashIDMap().get(resourceHash)); + + } + + for (Integer itemBaseID : resources) { + ItemBase ib = ItemBase.getItemBase(itemBaseID); + if (ib == null) + continue; + if (ruledWarehouse.isAboveCap(ib, (int) (city.getWarehouse().getResources().get(ib) * taxPercent))) { + ErrorPopupMsg.sendErrorMsg(player, "You're warehouse has enough " + ib.getName() + " already!"); + return true; + } + + } + + if (!city.setRealmTaxDate(LocalDateTime.now().plusDays(7))) { + ErrorPopupMsg.sendErrorMsg(player, "Failed to Update next Tax Date due to internal Error. City was not charged taxes this time."); + return false; + } + try { + city.getWarehouse().transferResources(player, msg, resources, taxPercent, ruledWarehouse); + } catch (Exception e) { + Logger.info(e.getMessage()); + } + + // Member variable assignment + + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + vrm.setGuild(building.getGuild()); + vrm.setWarehouseBuilding(BuildingManager.getBuildingFromCache(building.getCity().getWarehouse().getBuildingUID())); + vrm.configure(); + Dispatch dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + + } +} diff --git a/src/engine/objects/Colliders.java b/src/engine/objects/Colliders.java new file mode 100644 index 00000000..e1d46f7e --- /dev/null +++ b/src/engine/objects/Colliders.java @@ -0,0 +1,63 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.awt.geom.Line2D; + + +public class Colliders { + + + public float startX; + public float startY; + public float endX; + public float endY; + private Line2D collider; + private int doorID; + private boolean link = false; + + public Colliders(Line2D collider, int doorID, boolean link) { + super(); + this.collider = collider; + this.doorID = doorID; + this.link = link; + } + + public Colliders(float startX, float startY, float endX, float endY, int doorID, boolean link) { + super(); + this.startX = startX; + this.startY = startY; + this.endX = endX; + this.endY = endY; + this.doorID = doorID; + this.link = link; + } + + + public int getDoorID() { + return doorID; + } + + public Line2D getCollider() { + return collider; + } + + + public boolean isLink() { + return link; + } + + + public void setLink(boolean link) { + this.link = link; + } + + +} diff --git a/src/engine/objects/Condemned.java b/src/engine/objects/Condemned.java new file mode 100644 index 00000000..bdcb59a6 --- /dev/null +++ b/src/engine/objects/Condemned.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class Condemned { + + public static final int INDIVIDUAL = 2; + public static final int GUILD = 4; + public static final int NATION = 5; + private int ID; + private int playerUID; + private int parent; + private int guildUID; + private int friendType; + private boolean active; + + + /** + * ResultSet Constructor + */ + public Condemned(ResultSet rs) throws SQLException { + this.playerUID = rs.getInt("playerUID"); + this.parent = rs.getInt("buildingUID"); + this.guildUID = rs.getInt("guildUID"); + this.friendType = rs.getInt("friendType"); + this.active = rs.getBoolean("active"); + } + + + public Condemned(int playerUID, int parent, int guildUID, int friendType) { + super(); + this.playerUID = playerUID; + this.parent = parent; + this.guildUID = guildUID; + this.friendType = friendType; + this.active = false; + } + + + public int getPlayerUID() { + return playerUID; + } + + + public int getParent() { + return parent; + } + + + public int getGuildUID() { + return guildUID; + } + + + public int getFriendType() { + return friendType; + } + + public boolean isActive() { + return active; + } + + + public boolean setActive(boolean active) { + if (!DbManager.BuildingQueries.updateActiveCondemn(this, active)) + return false; + this.active = active; + return true; + } + + +} diff --git a/src/engine/objects/Contract.java b/src/engine/objects/Contract.java new file mode 100644 index 00000000..076e8525 --- /dev/null +++ b/src/engine/objects/Contract.java @@ -0,0 +1,260 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import ch.claude_martin.enumbitset.EnumBitSet; +import engine.Enum; +import engine.gameManager.DbManager; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class Contract extends AbstractGameObject { + + private final int contractID; + private final String name; + private final int mobbaseID; + private final int classID; + private final int extraRune; + private final int iconID; + public int equipmentSet = 0; + public int inventorySet = 0; + private int vendorID; + private boolean isTrainer; + private VendorDialog vendorDialog; + private ArrayList npcMenuOptions = new ArrayList<>(); + private ArrayList npcModTypeTable = new ArrayList<>(); + private ArrayList npcModSuffixTable = new ArrayList<>(); + private ArrayList itemModTable = new ArrayList<>(); + private ArrayList sellInventory = new ArrayList<>(); + private EnumBitSet allowedBuildings; + private ArrayList buyItemType = new ArrayList<>(); + private ArrayList buySkillToken = new ArrayList<>(); + private ArrayList buyUnknownToken = new ArrayList<>(); + + /** + * No Table ID Constructor + */ + public Contract(int contractID, String name, int mobbaseID, int classID, int dialogID, int iconID, int extraRune) { + super(); + this.contractID = contractID; + this.name = name; + this.mobbaseID = mobbaseID; + this.classID = classID; + this.iconID = iconID; + this.extraRune = extraRune; + this.vendorDialog = VendorDialog.getVendorDialog(dialogID); + setBools(); + } + + /** + * Normal Constructor + */ + public Contract(int contractID, String name, int mobbaseID, int classID, int dialogID, int iconID, int extraRune, int newUUID) { + super(newUUID); + this.contractID = contractID; + this.name = name; + this.mobbaseID = mobbaseID; + this.classID = classID; + this.iconID = iconID; + this.extraRune = extraRune; + this.vendorDialog = VendorDialog.getVendorDialog(dialogID); + setBools(); + } + + + /** + * ResultSet Constructor + */ + public Contract(ResultSet rs) throws SQLException { + super(rs); + this.contractID = rs.getInt("contractID"); + this.name = rs.getString("name"); + this.mobbaseID = rs.getInt("mobbaseID"); + this.classID = rs.getInt("classID"); + this.extraRune = rs.getInt("extraRune"); + this.vendorDialog = VendorDialog.getVendorDialog(rs.getInt("dialogID")); + this.iconID = rs.getInt("iconID"); + this.vendorID = rs.getInt("vendorID"); + this.allowedBuildings = EnumBitSet.asEnumBitSet(rs.getLong("allowedBuildingTypeID"), Enum.BuildingGroup.class); + this.equipmentSet = rs.getInt("equipSetID"); + this.inventorySet = rs.getInt("inventorySet"); + + try { + String menuoptions = rs.getString("menuoptions"); + + if (!menuoptions.isEmpty()) { + String[] data = menuoptions.split(" "); + for (String data1 : data) { + this.npcMenuOptions.add(Integer.parseInt(data1)); + } + } + + String modtypetable = rs.getString("pTable"); + if (!modtypetable.isEmpty()) { + String[] data = modtypetable.split(" "); + for (String data1 : data) { + this.npcModTypeTable.add(Integer.parseInt(data1)); + } + } + + String suffix = rs.getString("sTable"); + + if (!suffix.isEmpty()) { + String[] data1 = suffix.split(" "); + + for (String data11 : data1) { + this.npcModSuffixTable.add(Integer.parseInt(data11)); + } + } + + String itemMod = rs.getString("itemModTable"); + + if (!itemMod.isEmpty()) { + String[] data2 = itemMod.split(" "); + for (byte i = 0; i < data2.length; i++) { + this.itemModTable.add(Byte.parseByte(data2[i])); + } + + } + + } catch (SQLException | NumberFormatException e) { + Logger.error("Error when parsing mod tables"); + } + setBools(); + } + + //Specify if trainer, merchant, banker, etc via classID + private void setBools() { + DbManager.ContractQueries.LOAD_CONTRACT_INVENTORY(this); + DbManager.ContractQueries.LOAD_SELL_LIST_FOR_CONTRACT(this); + + this.isTrainer = this.classID > 2499 && this.classID < 3050 || this.classID == 2028; + + } + + /* + * Getters + */ + public int getContractID() { + return this.contractID; + } + + public String getName() { + return this.name; + } + + public int getMobbaseID() { + return this.mobbaseID; + } + + public int getClassID() { + return this.classID; + } + + public int getExtraRune() { + return this.extraRune; + } + + public boolean isTrainer() { + return this.isTrainer; + } + + public int getIconID() { + return this.iconID; + } + + public int getVendorID() { + return this.vendorID; + } + + public VendorDialog getVendorDialog() { + return this.vendorDialog; + } + + public ArrayList getNPCMenuOptions() { + return this.npcMenuOptions; + } + + public ArrayList getNPCModTypeTable() { + return this.npcModTypeTable; + } + + public ArrayList getNpcModSuffixTable() { + return npcModSuffixTable; + } + + public ArrayList getItemModTable() { + return itemModTable; + } + + public ArrayList getSellInventory() { + return this.sellInventory; + } + + public int getPromotionClass() { + if (this.classID < 2504 || this.classID > 2526) + return 0; + return this.classID; + } + + public boolean isRuneMaster() { + return (this.classID == 850); + } + + public boolean isArtilleryCaptain() { + return this.contractID == 839 || this.contractID == 842; + } + + @Override + public void updateDatabase() { + DbManager.ContractQueries.updateDatabase(this); + } + + public EnumBitSet getAllowedBuildings() { + return allowedBuildings; + } + + public ArrayList getBuyItemType() { + return this.buyItemType; + } + + public ArrayList getBuySkillToken() { + return this.buySkillToken; + } + + public ArrayList getBuyUnknownToken() { + return this.buyUnknownToken; + } + + public boolean canSlotinBuilding(Building building) { + + // Need a building to slot in a building! + if (building == null) + return false; + + // Can't slot in anything but a blueprinted building + if (building.getBlueprintUUID() == 0) + return false; + + // No buildings no slotting + if (this.allowedBuildings.size() == 0) + return false; + + // Binary match + return (building.getBlueprint().getBuildingGroup().elementOf(this.allowedBuildings)); + } + + public int getEquipmentSet() { + return equipmentSet; + } +} diff --git a/src/engine/objects/Corpse.java b/src/engine/objects/Corpse.java new file mode 100644 index 00000000..fa7970ad --- /dev/null +++ b/src/engine/objects/Corpse.java @@ -0,0 +1,403 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.GameObjectType; +import engine.Enum.GridObjectType; +import engine.Enum.ItemType; +import engine.InterestManagement.WorldGrid; +import engine.exception.SerializationException; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.RemoveCorpseJob; +import engine.net.ByteBufferWriter; +import engine.net.DispatchMessage; +import engine.net.client.msg.UnloadObjectsMsg; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class Corpse extends AbstractWorldObject { + + private static AtomicInteger corpseCounter = new AtomicInteger(0); + public JobContainer cleanup; + private String firstName; + private String lastName; + private int level; + private int belongsToType; + private int belongsToID; + private ArrayList inventory; + private boolean asciiLastName = true; + private boolean hasGold = false; + private int inBuildingID = 0; + private int inFloorID = -1; + private int inBuilding = -1; + + /** + * No Id Constructor + */ + public Corpse(int newUUID, AbstractCharacter belongsTo, boolean safeZone, boolean enterWorld) { + super(newUUID); + this.setObjectType(); + this.inventory = new ArrayList<>(); + this.gridObjectType = GridObjectType.STATIC; + this.setObjectTypeMask(MBServerStatics.MASK_CORPSE); + if (belongsTo != null) { + this.firstName = belongsTo.getFirstName(); + this.lastName = belongsTo.getLastName(); + this.asciiLastName = belongsTo.asciiLastName(); + this.level = belongsTo.getLevel(); + this.belongsToType = belongsTo.getObjectType().ordinal(); + this.belongsToID = belongsTo.getObjectUUID(); + this.inBuilding = belongsTo.getInBuilding(); + this.inFloorID = belongsTo.getInFloorID(); + this.inBuildingID = belongsTo.getInBuildingID(); + this.setLoc(belongsTo.getLoc()); + } else { + Logger.error("No player passed in for corpse"); + this.firstName = ""; + this.lastName = ""; + this.level = 1; + this.belongsToType = 0; + this.belongsToID = 0; + } + this.setObjectTypeMask(MBServerStatics.MASK_CORPSE); + + if (!safeZone) + transferInventory(belongsTo, enterWorld); + + + } + + public static int getNextCorpseCount() { + return Corpse.corpseCounter.addAndGet(2); //newUUID and runeID + } + + //Create a new corpse + public static Corpse makeCorpse(AbstractCharacter belongsTo, boolean enterWorld) { + boolean safeZone = false; + if (belongsTo != null && belongsTo.getObjectType() == GameObjectType.PlayerCharacter) + safeZone = ((PlayerCharacter) belongsTo).isInSafeZone(); + + + Corpse corpse = new Corpse(Corpse.getNextCorpseCount(), belongsTo, safeZone, enterWorld); + + //create cleanup job + if (corpse != null) { + RemoveCorpseJob rcj = new RemoveCorpseJob(corpse); + corpse.cleanup = JobScheduler.getInstance().scheduleJob(rcj, MBServerStatics.CORPSE_CLEANUP_TIMER_MS); + DbManager.addToCache(corpse); + } + + return corpse; + } + + //Get existing corpse + public static Corpse getCorpse(int newUUID) { + return (Corpse) DbManager.getFromCache(GameObjectType.Corpse, newUUID); + } + + //remove corpse from world + public static void removeCorpse(int newUUID, boolean fromTimer) { + Corpse c = (Corpse) DbManager.getFromCache(GameObjectType.Corpse, newUUID); + if (c == null) + Logger.error("No corpse found of ID " + newUUID); + else + Corpse.removeCorpse(c, fromTimer); + } + + public static void removeCorpse(Corpse corpse, boolean fromTimer) { + if (corpse == null) + return; + + corpse.purgeInventory(); + + //cleanup timer + if (!fromTimer) { + JobScheduler.getInstance().cancelScheduledJob(corpse.cleanup); + } + corpse.cleanup = null; + + //Remove from world + UnloadObjectsMsg uom = new UnloadObjectsMsg(); + uom.addObject(corpse); + DispatchMessage.sendToAllInRange(corpse, uom); + WorldGrid.RemoveWorldObject(corpse); + + //clear from cache + DbManager.removeFromCache(corpse); + } + + public static void _serializeForClientMsg(Corpse corpse, ByteBufferWriter writer) + throws SerializationException { + } + + public static void _serializeForClientMsg(Corpse corpse, ByteBufferWriter writer, boolean aln) + throws SerializationException { + + Building building = null; + if (corpse.inBuildingID != 0) + building = BuildingManager.getBuildingFromCache(corpse.inBuildingID); + + //Send Rune Count + writer.putInt(0); + writer.putInt(0); + writer.putInt(1); + + //Send Corpse Rune + writer.putInt(1); + writer.putInt(0); + writer.putInt(MBServerStatics.TOMBSTONE); + writer.putInt(corpse.getObjectType().ordinal()); + writer.putInt((corpse.getObjectUUID() + 1)); + + //Send Stats + writer.putInt(5); + writer.putInt(MBServerStatics.STAT_STR_ID); // Strength ID + writer.putInt(5000); + writer.putInt(MBServerStatics.STAT_SPI_ID); // Spirit ID + writer.putInt(0); + writer.putInt(MBServerStatics.STAT_CON_ID); // Constitution ID + writer.putInt(0); + writer.putInt(MBServerStatics.STAT_DEX_ID); // Dexterity ID + writer.putInt(0); + writer.putInt(MBServerStatics.STAT_INT_ID); // Intelligence ID + writer.putInt(0); + + //Send Name + writer.putString(corpse.firstName); + if (aln && !corpse.asciiLastName) + writer.putString(""); + else + writer.putString(corpse.lastName); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 1); + + //Send Corpse Info + writer.putInt(0); + writer.putInt(corpse.getObjectType().ordinal()); + writer.putInt((corpse.getObjectUUID())); + writer.putFloat(10f); //FaceDir or scale + writer.putFloat(10); //FaceDir or scale + writer.putFloat(10); //FaceDir or scale + + writer.putFloat(corpse.getLoc().x); + writer.putFloat(corpse.getLoc().y); + writer.putFloat(corpse.getLoc().z); + + writer.putFloat(6.235f); //1.548146f); //w + writer.putInt(0); + writer.putInt(0); + + //Send BelongsToInfo + writer.putInt(((corpse.level / 10))); //Rank + writer.putInt(corpse.level); //Level + writer.putInt(1); + writer.putInt(1); + writer.putInt(1); //Missing this? + writer.putInt(2); + writer.putInt(1); + // writer.putInt(0); //not needed? + writer.putInt(0); + + writer.putInt(corpse.belongsToType); + writer.putInt(corpse.belongsToID); + + writer.putInt(0); + writer.putInt(0); + + + for (int i = 0; i < 9; i++) + writer.putInt(0); + writer.putShort((short) 0); + writer.put((byte) 0); + + //Send Errant Guild Info + for (int i = 0; i < 13; i++) + writer.putInt(0); + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); //Missing this? + writer.putInt(16); + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + //Send unknown counter + writer.putInt(1); + writer.putInt(0x047A0E67); //What is this? + writer.put((byte) 0); + + //Send unknown + writer.putInt(0); + writer.putInt(0); + writer.putFloat(1293.4449f); //Unknown + writer.putFloat(-100f); //Unknown + writer.putInt(0); + writer.put((byte) 0); + + writer.put((byte) 0); //End datablock + + } + + public boolean removeItemFromInventory(Item item) { + synchronized (this.inventory) { + if (this.inventory.contains(item)) { + this.inventory.remove(item); + return true; + } + return false; + } + } + + public void transferInventory(AbstractCharacter belongsTo, boolean enterWorld) { + if (belongsTo == null) { + Logger.error("Can't find player that corpse " + this.getObjectUUID() + " belongs to"); + return; + } + + //TODO transfer items from players inventory and trade window to corpse + CharacterItemManager cim = belongsTo.getCharItemManager(); + if (cim != null) + cim.transferEntireInventory(this.inventory, this, enterWorld); + else + Logger.error("Can't find inventory for player " + belongsTo.getObjectUUID()); + } + + public Item lootItem(Item i, PlayerCharacter looter) { + //make sure looter exists + if (looter == null) + return null; + + //get looters item manager + CharacterItemManager looterItems = looter.getCharItemManager(); + if (looterItems == null) + return null; + + synchronized (this.inventory) { + + //make sure player has item in inventory + if (!this.inventory.contains(i)) + return null; + + //get weight of item + ItemBase ib = i.getItemBase(); + if (ib == null) + return null; + short weight = ib.getWeight(); + + //make sure looter has room for item + if (ib.getType().equals(ItemType.GOLD) == false && !looterItems.hasRoomInventory(weight)) + return null; + + //attempt to transfer item in db + if (ib.getType().equals(ItemType.GOLD)) { + if (!looterItems.moveGoldToInventory(i, i.getNumOfItems())) + return null; + } else if (!i.moveItemToInventory(looter)) + return null; + + //db transfer successful, remove from this character + this.inventory.remove(this.inventory.indexOf(i)); + } + + //add item to looter. + if (!looterItems.addItemToInventory(i)) + return null; + + //calculate new weights + looterItems.calculateInventoryWeight(); + + return i; + } + + public boolean hasGold() { + return this.hasGold; + } + + public void setHasGold(boolean value) { + this.hasGold = value; + } + + public ArrayList getInventory() { + synchronized (this.inventory) { + return this.inventory; + } + } + + /** + * Delete and remove all items in the inventory + */ + private void purgeInventory() { + //make a copy so we're not inside synchronized{} while waiting for all items to be junked + ArrayList inventoryCopy; + synchronized (this.inventory) { + inventoryCopy = new ArrayList<>(this.inventory); + this.inventory.clear(); + } + + for (Item item : inventoryCopy) { + item.junk(); + } + } + + @Override + public void updateDatabase() { + } + + @Override + public void runAfterLoad() { + } + + public int getBelongsToType() { + return this.belongsToType; + } + + public int getBelongsToID() { + return this.belongsToID; + } + + @Override + public String getName() { + if (this.firstName.length() == 0) { + return "Unknown corpse"; + } + if (this.lastName.length() == 0) { + return this.firstName; + } + return this.firstName + ' ' + this.lastName; + } + + public int getInBuilding() { + return inBuilding; + } + + public void setInBuilding(int inBuilding) { + this.inBuilding = inBuilding; + } + + public int getInFloorID() { + return inFloorID; + } + + public void setInFloorID(int inFloorID) { + this.inFloorID = inFloorID; + } +} diff --git a/src/engine/objects/Effect.java b/src/engine/objects/Effect.java new file mode 100644 index 00000000..60e991c8 --- /dev/null +++ b/src/engine/objects/Effect.java @@ -0,0 +1,663 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.DamageType; +import engine.Enum.EffectSourceType; +import engine.gameManager.PowersManager; +import engine.job.AbstractJob; +import engine.job.AbstractScheduleJob; +import engine.job.JobContainer; +import engine.jobs.AbstractEffectJob; +import engine.jobs.DamageOverTimeJob; +import engine.jobs.NoTimeJob; +import engine.jobs.PersistentAoeJob; +import engine.net.ByteBufferWriter; +import engine.net.client.ClientConnection; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import engine.powers.effectmodifiers.AbstractEffectModifier; + +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicBoolean; + + +public class Effect { + + private JobContainer jc; + + //Fail Conditions + private boolean cancelOnAttack; + private boolean cancelOnAttackSwing; + private boolean cancelOnCast; + private boolean cancelOnCastSpell; + private boolean cancelOnEquipChange; + private boolean cancelOnLogout; + private boolean cancelOnMove; + private boolean cancelOnNewCharm; + private boolean cancelOnSit; + private boolean cancelOnTakeDamage; + private boolean cancelOnTerritoryClaim; + private boolean cancelOnUnEquip; + private boolean cancelOnStun; + private boolean bakedInStat = false; + private boolean isStatic = false; + private int effectSourceType = 0; + private int effectSourceID = 0; + + + private EffectsBase eb; + private int trains; + private float damageAmount = 0f; + + private AtomicBoolean cancel = new AtomicBoolean(false); + + //private AbstractWorldObject owner; + + /** + * Generic Constructor + */ + public Effect(JobContainer jc, EffectsBase eb, int trains) { + this.jc = jc; + this.cancelOnAttack = false; + this.cancelOnAttackSwing = false; + this.cancelOnCast = false; + this.cancelOnCastSpell = false; + this.cancelOnEquipChange = false; + this.cancelOnLogout = false; + this.cancelOnMove = false; + this.cancelOnNewCharm = false; + this.cancelOnSit = false; + this.cancelOnTakeDamage = false; + this.cancelOnTerritoryClaim = false; + this.cancelOnUnEquip = false; + this.cancelOnStun = false; + this.eb = eb; + this.trains = trains; + } + + public Effect(JobContainer jc, EffectsBase eb, int trains, boolean isStatic) { + this.jc = jc; + this.cancelOnAttack = false; + this.cancelOnAttackSwing = false; + this.cancelOnCast = false; + this.cancelOnCastSpell = false; + this.cancelOnEquipChange = false; + this.cancelOnLogout = false; + this.cancelOnMove = false; + this.cancelOnNewCharm = false; + this.cancelOnSit = false; + this.cancelOnTakeDamage = false; + this.cancelOnTerritoryClaim = false; + this.cancelOnUnEquip = false; + this.cancelOnStun = false; + this.eb = eb; + this.trains = trains; + this.isStatic = isStatic; + } + + private static void blankFill(ByteBufferWriter writer) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } + + //called when effect ends. Send message to client to remove effect + public void endEffect() { + if (this.jc != null) { + AbstractJob aj = jc.getJob(); + if (aj == null) + return; + if (aj instanceof AbstractEffectJob) { + ((AbstractEffectJob) aj).setSkipCancelEffect(false); + ((AbstractEffectJob) aj).endEffect(); + } + } + } + + public void endEffectNoPower() { + if (this.jc != null) { + AbstractJob aj = jc.getJob(); + if (aj == null) + return; + if (aj instanceof AbstractEffectJob) { + ((AbstractEffectJob) aj).setSkipCancelEffect(false); + ((AbstractEffectJob) aj).endEffectNoPower(); + } + } + } + + //Called when effect ends before timer done + public void cancelJob() { + if (this.jc != null) { + AbstractJob aj = jc.getJob(); + if (aj == null) + return; + if (aj instanceof AbstractEffectJob) + ((AbstractEffectJob) aj).setSkipCancelEffect(false); + if (aj instanceof AbstractScheduleJob) { + ((AbstractScheduleJob) aj).cancelJob(); + } + } + } + + public void cancelJob(boolean skipEffect) { + if (this.jc != null) { + AbstractJob aj = jc.getJob(); + if (aj == null) + return; + if (skipEffect && aj instanceof AbstractEffectJob) { + ((AbstractEffectJob) aj).setSkipCancelEffect(skipEffect); + } + if (aj instanceof AbstractScheduleJob) { + ((AbstractScheduleJob) aj).cancelJob(); + } + } + } + + public boolean applyBonus(Item item) { + if (this.jc == null) + return false; + AbstractJob aj = jc.getJob(); + if (aj == null) + return false; + if (aj instanceof AbstractEffectJob) { + AbstractEffectJob aej = (AbstractEffectJob) aj; + EffectsBase eb = aej.getEffect(); + if (eb == null) + return false; + HashSet aems = eb.getModifiers(); + for (AbstractEffectModifier aem : aems) + aem.applyBonus(item, aej.getTrains()); + return true; + } + return false; + } + + public boolean applyBonus(Building building) { + if (this.jc == null) + return false; + AbstractJob aj = jc.getJob(); + if (aj == null) + return false; + if (aj instanceof AbstractEffectJob) { + AbstractEffectJob aej = (AbstractEffectJob) aj; + EffectsBase eb = aej.getEffect(); + if (eb == null) + return false; + HashSet aems = eb.getModifiers(); + for (AbstractEffectModifier aem : aems) + aem.applyBonus(building, aej.getTrains()); + return true; + } + return false; + } + + public boolean applyBonus(AbstractCharacter ac) { + if (this.jc == null) + return false; + AbstractJob aj = jc.getJob(); + if (aj == null) + return false; + if (aj instanceof AbstractEffectJob) { + AbstractEffectJob aej = (AbstractEffectJob) aj; + EffectsBase eb = aej.getEffect(); + if (eb == null) + return false; + HashSet aems = eb.getModifiers(); + for (AbstractEffectModifier aem : aems) + aem.applyBonus(ac, aej.getTrains()); + return true; + } + return false; + } + + public boolean applyBonus(Item item, AbstractCharacter ac) { + if (this.jc == null) + return false; + AbstractJob aj = jc.getJob(); + if (aj == null) + return false; + if (aj instanceof AbstractEffectJob) { + AbstractEffectJob aej = (AbstractEffectJob) aj; + EffectsBase eb = aej.getEffect(); + if (eb == null) + return false; + HashSet aems = eb.getModifiers(); + for (AbstractEffectModifier aem : aems) { + aem.applyBonus(item, aej.getTrains()); + aem.applyBonus(ac, aej.getTrains()); + } + return true; + } + return false; + } + + public HashSet getEffectModifiers() { + if (this.jc == null) + return null; + AbstractJob aj = jc.getJob(); + if (aj == null) + return null; + if (aj instanceof AbstractEffectJob) { + AbstractEffectJob aej = (AbstractEffectJob) aj; + EffectsBase eb = aej.getEffect(); + if (eb == null) + return null; + return eb.getModifiers(); + } + return null; + } + + //Send this effect to a client when loading a player + public void sendEffect(ClientConnection cc) { + if (this.jc == null || this.eb == null || cc == null) + return; + AbstractJob aj = this.jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) + return; + this.eb.sendEffect((AbstractEffectJob) aj, (this.jc.timeToExecutionLeft() / 1000), cc); + } + + public void sendEffectNoPower(ClientConnection cc) { + if (this.jc == null || this.eb == null || cc == null) + return; + AbstractJob aj = this.jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) + return; + this.eb.sendEffectNoPower((AbstractEffectJob) aj, (this.jc.timeToExecutionLeft() / 1000), cc); + } + + public void sendSpireEffect(ClientConnection cc, boolean onEnter) { + if (this.jc == null || this.eb == null || cc == null) + return; + AbstractJob aj = this.jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) + return; + int duration = 45; + if (onEnter) + duration = -1; + this.eb.sendEffectNoPower((AbstractEffectJob) aj, duration, cc); + } + + public void serializeForItem(ByteBufferWriter writer, Item item) { + if (this.jc == null) { + blankFill(writer); + return; + } + AbstractJob aj = this.jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) { + blankFill(writer); + return; + } + AbstractEffectJob aej = (AbstractEffectJob) aj; + PowersBase pb = aej.getPower(); + ActionsBase ab = aej.getAction(); + if (this.eb == null) { + blankFill(writer); + return; + } else if (pb == null && !(this.jc.noTimer())) { + blankFill(writer); + return; + } + if (this.jc.noTimer()) { + if (pb == null) + writer.putInt(this.eb.getToken()); + else + writer.putInt(pb.getToken()); + writer.putInt(aej.getTrains()); + writer.putInt(1); + writer.put((byte) 1); + writer.putInt(item.getObjectType().ordinal()); + writer.putInt(item.getObjectUUID()); + + writer.putString(item.getName()); + writer.putFloat(-1000f); + } else { + float duration = this.jc.timeToExecutionLeft() / 1000; + writer.putInt(this.eb.getToken()); + writer.putInt(aej.getTrains()); + writer.putInt(0); + writer.put((byte) 0); + writer.putInt(pb.getToken()); + writer.putString(pb.getName()); + writer.putFloat(duration); + } + } + + public void serializeForClientMsg(ByteBufferWriter writer) { + AbstractJob aj = this.jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) { + //TODO put error message here + blankFill(writer); + return; + } + AbstractEffectJob aej = (AbstractEffectJob) aj; + PowersBase pb = aej.getPower(); + ActionsBase ab = aej.getAction(); + if (ab == null || pb == null || this.eb == null) { + //TODO put error message here + blankFill(writer); + return; + } + + if (aej instanceof PersistentAoeJob) { + blankFill(writer); + return; + } + + float duration = this.jc.timeToExecutionLeft() / 1000; + if (aej instanceof DamageOverTimeJob) + duration = ab.getDurationInSeconds(aej.getTrains()) - (((DamageOverTimeJob) aej).getIteration() * 5); + + + writer.putInt(pb.getToken()); + writer.putInt(aej.getTrains()); + writer.putInt(0); + writer.put((byte) 0); + writer.putInt(this.eb.getToken()); + writer.putString(pb.getName()); + writer.putFloat(duration); + } + + public boolean serializeForLoad(ByteBufferWriter writer) { + AbstractJob aj = this.jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) { + return false; + } + + + AbstractEffectJob aej = (AbstractEffectJob) aj; + PowersBase pb = aej.getPower(); + ActionsBase ab = aej.getAction(); + if (this.eb == null) { + return false; + } + if (aej instanceof PersistentAoeJob) { + return false; + } + + + float duration = this.jc.timeToExecutionLeft() / 1000; + if (aej instanceof DamageOverTimeJob) + if (ab != null) + duration = ab.getDurationInSeconds(aej.getTrains()) - (((DamageOverTimeJob) aej).getIteration() * 5); + if (aej instanceof NoTimeJob) + duration = -1; + + int sendToken = this.getEffectToken(); + + if (aej.getAction() != null) + if (aej.getAction().getPowerAction() != null + && PowersManager.ActionTokenByIDString.containsKey(aej.getAction().getPowerAction().getIDString())) + try { + sendToken = PowersManager.ActionTokenByIDString.get(aej.getAction().getPowerAction().getIDString()); + } catch (Exception e) { + sendToken = this.getEffectToken(); + } + + + writer.putInt(sendToken); + writer.putInt(this.trains); + writer.putInt(0); //? + if (aej.getEffectSourceID() != 0) { + writer.put((byte) 1); + writer.putInt(aej.getEffectSourceType()); + writer.putInt(aej.getEffectSourceID()); + } else { + writer.put((byte) 0); + writer.putInt(pb != null ? pb.getToken() : 0); + } + writer.putString(pb != null ? pb.getName() : eb.getName()); + + writer.putFloat(duration); + return true; + } + + public float getDuration() { + float duration = 0f; + if (this.jc != null) + duration = this.jc.timeToExecutionLeft() / 1000; + return duration; + } + + public boolean containsSource(EffectSourceType source) { + if (this.eb != null) + return this.eb.containsSource(source); + return false; + } + + public JobContainer getJobContainer() { + return this.jc; + } + + public int getTrains() { + return this.trains; + } + + public void setTrains(int value) { + this.trains = value; + } + + public float getDamageAmount() { + return this.damageAmount; + } + + public AbstractJob getJob() { + if (this.jc == null) + return null; + return jc.getJob(); + } + + public boolean bakedInStat() { + return this.bakedInStat; + } + + public void setBakedInStat(boolean value) { + this.bakedInStat = value; + } + + public PowersBase getPower() { + if (this.jc == null) + return null; + AbstractJob aj = jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) + return null; + return ((AbstractEffectJob) aj).getPower(); + } + + public int getPowerToken() { + if (this.jc == null) + return 0; + AbstractJob aj = jc.getJob(); + if (aj == null || (!(aj instanceof AbstractEffectJob))) + return 0; + PowersBase pb = ((AbstractEffectJob) aj).getPower(); + if (pb == null) + return 0; + return pb.getToken(); + } + + public int getEffectToken() { + if (this.eb != null) + return this.eb.getToken(); + return 0; + } + + public EffectsBase getEffectsBase() { + return this.eb; + } + + public String getName() { + if (this.jc == null) + return ""; + AbstractJob aj = this.jc.getJob(); + if (aj == null || !(aj instanceof AbstractEffectJob)) + return ""; + AbstractEffectJob aej = (AbstractEffectJob) aj; + PowersBase pb = aej.getPower(); + if (pb == null) + return ""; + return pb.getName(); + } + + public boolean cancel() { + return this.cancel.compareAndSet(false, true); + } + + public boolean canceled() { + return this.cancel.get(); + } + + public boolean cancelOnAttack() { + if (this.eb == null) + return true; + return this.eb.cancelOnAttack(); + } + + public boolean cancelOnAttackSwing() { + if (this.eb == null) + return true; + return this.eb.cancelOnAttackSwing(); + } + + public boolean cancelOnCast() { + if (this.eb == null) + return true; + return this.eb.cancelOnCast(); + } + + public boolean cancelOnCastSpell() { + if (this.eb == null) + return true; + return this.eb.cancelOnCastSpell(); + } + + public boolean cancelOnEquipChange() { + if (this.eb == null) + return true; + return this.eb.cancelOnEquipChange(); + } + + public boolean cancelOnLogout() { + if (this.eb == null) + return true; + return this.eb.cancelOnLogout(); + } + + public boolean cancelOnMove() { + if (this.eb == null || this.cancelOnMove) + return true; + return this.eb.cancelOnMove(); + } + + public boolean cancelOnNewCharm() { + if (this.eb == null) + return true; + return this.eb.cancelOnNewCharm(); + } + + public boolean cancelOnSit() { + if (this.eb == null) + return true; + return this.eb.cancelOnSit(); + } + + public boolean cancelOnStun() { + if (this.eb == null) + return true; + return this.cancelOnStun; + } + + public boolean cancelOnTakeDamage() { + if (this.eb == null) + return true; + if (this.eb.damageTypeSpecific()) { + return false; //handled in call from resists + } else { + return this.eb.cancelOnTakeDamage(); + } + } + + //Used for verifying when damage absorbers fails + public boolean cancelOnTakeDamage(DamageType type, float amount) { + if (!this.eb.cancelOnTakeDamage()) + return false; + if (this.eb == null || amount < 0f) + return false; + if (this.eb.damageTypeSpecific()) { + if (type == null) + return false; + if (this.eb.containsDamageType(type)) { + this.damageAmount += amount; + return this.damageAmount > this.eb.getDamageAmount(this.trains); + } else + return false; + } else + return false; //handled by call from AbstractCharacter + } + + public boolean isDamageAbsorber() { + if (this.eb == null) + return false; + if (!this.eb.cancelOnTakeDamage()) + return false; + return this.eb.damageTypeSpecific(); + } + + public boolean cancelOnTerritoryClaim() { + if (this.eb == null) + return true; + return this.eb.cancelOnTerritoryClaim(); + } + + public boolean cancelOnUnEquip() { + if (this.eb == null) + return true; + return this.eb.cancelOnUnEquip(); + } + + public void setPAOE() { + this.cancelOnStun = true; + this.cancelOnMove = true; + } + + public boolean isStatic() { + return isStatic; + } + + public void setIsStatic(boolean isStatic) { + this.isStatic = isStatic; + + } + + public int getEffectSourceID() { + return effectSourceID; + } + + public void setEffectSourceID(int effectSourceID) { + this.effectSourceID = effectSourceID; + } + + public int getEffectSourceType() { + return effectSourceType; + } + + public void setEffectSourceType(int effectSourceType) { + this.effectSourceType = effectSourceType; + } + + +} \ No newline at end of file diff --git a/src/engine/objects/EffectsResourceCosts.java b/src/engine/objects/EffectsResourceCosts.java new file mode 100644 index 00000000..17547d1d --- /dev/null +++ b/src/engine/objects/EffectsResourceCosts.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class EffectsResourceCosts extends AbstractGameObject { + + private String IDString; + private int resourceID; + private int amount; + private int UID; + + /** + * No Table ID Constructor + */ + public EffectsResourceCosts() { + + } + + /** + * ResultSet Constructor + */ + public EffectsResourceCosts(ResultSet rs) throws SQLException { + + this.UID = rs.getInt("UID"); + this.IDString = rs.getString("IDString"); + this.resourceID = rs.getInt("resource"); + this.amount = rs.getInt("amount"); + } + + + public String getIDString() { + return this.IDString; + } + + + public int getAmount() { + return this.amount; + } + + + public int getResourceID() { + return resourceID; + } + + @Override + public void removeFromCache() { + // TODO Auto-generated method stub + + } + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + + } + + public int getUID() { + return UID; + } + + +} diff --git a/src/engine/objects/EnchantmentBase.java b/src/engine/objects/EnchantmentBase.java new file mode 100644 index 00000000..73f5d801 --- /dev/null +++ b/src/engine/objects/EnchantmentBase.java @@ -0,0 +1,92 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class EnchantmentBase extends AbstractGameObject { + + private final String name; + private final String prefix; + private final String suffix; + + private final byte attributeID; + private final int modValue; + + /** + * No Table ID Constructor + */ + public EnchantmentBase(String name, String prefix, String suffix, + byte attributeID, int modValue) { + super(); + this.name = name; + this.prefix = prefix; + this.suffix = suffix; + this.attributeID = attributeID; + this.modValue = modValue; + } + + /** + * Normal Constructor + */ + public EnchantmentBase(String name, String prefix, String suffix, + byte attributeID, int modValue, int newUUID) { + super(newUUID); + this.name = name; + this.prefix = prefix; + this.suffix = suffix; + this.attributeID = attributeID; + this.modValue = modValue; + } + + /** + * ResultSet Constructor + */ + public EnchantmentBase(ResultSet rs) throws SQLException { + super(rs); + + this.name = rs.getString("name"); + this.prefix = rs.getString("prefix"); + this.suffix = rs.getString("suffix"); + this.attributeID = rs.getByte("attributeID"); + this.modValue = rs.getInt("modValue"); + + } + + /* + * Getters + */ + public String getName() { + return name; + } + + public String getPrefix() { + return prefix; + } + + public String getSuffix() { + return suffix; + } + + public byte getAttributeID() { + return attributeID; + } + + public int getModValue() { + return modValue; + } + + @Override + public void updateDatabase() { + // TODO Create update logic. + } +} diff --git a/src/engine/objects/Experience.java b/src/engine/objects/Experience.java new file mode 100644 index 00000000..3d33c68d --- /dev/null +++ b/src/engine/objects/Experience.java @@ -0,0 +1,454 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.TargetColor; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.server.MBServerStatics; + +import java.util.ArrayList; +import java.util.TreeMap; + +import static engine.gameManager.LootManager.LOOTMANAGER; + +public class Experience { + + private static final TreeMap ExpToLevel; + private static final int[] LevelToExp = {Integer.MIN_VALUE, // Pad + // everything + // over 1 + + // R0 + 0, // Level 1 + 150, // Level 2 + 1200, // Level 3 + 4050, // Level 4 + 9600, // Level 5 + 18750, // Level 6 + 32400, // Level 7 + 51450, // Level 8 + 76800, // Level 9 + + // R1 + 109350, // Level 10 + 150000, // Level 11 + 199650, // Level 12 + 259200, // Level 13 + 329550, // Level 14 + 411600, // Level 15 + 506250, // Level 16 + 614400, // Level 17 + 736950, // Level 18 + 874800, // Level 19 + + // R2 + 1028850, // Level 20 + 1200000, // Level 21 + 1389150, // Level 22 + 1597200, // Level 23 + 1825050, // Level 24 + 2073600, // Level 25 + 2343750, // Level 26 + 2636400, // Level 27 + 2952450, // Level 28 + 3292800, // Level 29 + + // R3 + 3658350, // Level 30 + 4050000, // Level 31 + 4468650, // Level 32 + 4915200, // Level 33 + 5390550, // Level 34 + 5895600, // Level 35 + 6431250, // Level 36 + 6998400, // Level 37 + 7597950, // Level 38 + 8230800, // Level 39 + + // R4 + 8897850, // Level 40 + 10091520, // Level 41 + 11396777, // Level 42 + 12820187, // Level 43 + 14368505, // Level 44 + 16048666, // Level 45 + 17867790, // Level 46 + 19833183, // Level 47 + 21952335, // Level 48 + 24232919, // Level 49 + + // R5 + 26682793, // Level 50 + 29310000, // Level 51 + 32122766, // Level 52 + 35129502, // Level 53 + 38338805, // Level 54 + 41759452, // Level 55 + 45400409, // Level 56 + 49270824, // Level 57 + 53380030, // Level 58 + 57737542, // Level 59 + + // R6 + 62353064, // Level 60 + 67236479, // Level 61 + 72397859, // Level 62 + 77847457, // Level 63 + 83595712, // Level 64 + 89653247, // Level 65 + 96030869, // Level 66 + 102739569, // Level 67 + 109790524, // Level 68 + 117195093, // Level 69 + + // R7 + 124964822, // Level 70 + 133111438, // Level 71 + 141646855, // Level 72 + 150583171, // Level 73 + 159932666, // Level 74 + 169707808, // Level 75 + 179921247, // Level 76 + + }; + + private static final float[] MaxExpPerLevel = {Float.MIN_VALUE, // Pad + // everything + // over + // 1 + + // R0 + 15, // Level 1 + 105, // Level 2 + 285, // Level 3 + 555, // Level 4 + 610, // Level 5 + 682.5f, // Level 6 + 730, // Level 7 + 975, // Level 8 + 1251.92f, // Level 9 + + // R1 + 1563.46f, // Level 10 + 1909.62f, // Level 11 + 2290.38f, // Level 12 + 2705.77f, // Level 13 + 3155.77f, // Level 14 + 3640.38f, // Level 15 + 4159.62f, // Level 16 + 4713.46f, // Level 17 + 5301.92f, // Level 18 + 5925, // Level 19 + + // R2 + 6582.69f, // Level 20 + 7275, // Level 21 + 8001.92f, // Level 22 + 8763.46f, // Level 23 + 9559.62f, // Level 24 + 10390.38f, // Level 25 + 11255.77f, // Level 26 + 12155.77f, // Level 27 + 13090.38f, // Level 28 + 14059.62f, // Level 29 + + // R3 + 15063.46f, // Level 30 + 16101.92f, // Level 31 + 17175, // Level 32 + 18282.69f, // Level 33 + 19425, // Level 34 + 20601.92f, // Level 35 + 21813.46f, // Level 36 + 23059.62f, // Level 37 + 24340.38f, // Level 38 + 25655.77f, // Level 39 + + // R4 + 45910.38f, // Level 40 + 34348.87f, // Level 41 + 37458.16f, // Level 42 + 40745.21f, // Level 43 + 44214.76f, // Level 44 + 47871.68f, // Level 45 + 51720.87f, // Level 46 + 55767.16f, // Level 47 + 60015.37f, // Level 48 + 64470.37f, // Level 49 + + // R5 + 69137.03f, // Level 50 + 74020.16f, // Level 51 + 79124.63f, // Level 52 + 84455.34f, // Level 53 + 90017.03f, // Level 54 + 95814.66f, // Level 55 + 101853.03f, // Level 56 + 108137, // Level 57 + 114671.37f, // Level 58 + 121461.11f, // Level 59 + + // R6 + 128510.92f, // Level 60 + 135825.79f, // Level 61 + 143410.47f, // Level 62 + 151269.87f, // Level 63 + 159408.82f, // Level 64 + 167832.16f, // Level 65 + 176544.74f, // Level 66 + 185551.45f, // Level 67 + 194857.08f, // Level 68 + 204466.55f, // Level 69 + + // R7 + 214384.63f, // Level 70 + 224616.24f, // Level 71 + 235166.21f, // Level 72 + 246039.34f, // Level 73 + 257240.58f, // Level 74 + 1 // 268774.71 //Level 75 + + }; + // Used to calcuate the amount of experience a monster grants in the + // following formula + // expGranted = a(moblevel)^2 + b(moblevel) + c + private static final float EXPQUADA = 10.0f; + private static final float EXPQUADB = 6.0f; + private static final float EXPQUADC = -10.0f; + // Adds addtional exp per addtional member of a group using the following + // (expGranted / group.size()) * (groupBonus * (group.size()-1) +1) + private static final float GROUP_BONUS = 0.5f; // 0.2 grants (20%) addtional + + static { + ExpToLevel = new TreeMap<>(); + + // flip keys and values for other Map + for (int i = 1; i < LevelToExp.length; i++) { + ExpToLevel.put(LevelToExp[i], i); + } + } // end Static block + // exp per group member + + // called to determine current level based on xp + public static int getLevel(int experience) { + + int expKey = ExpToLevel.floorKey(experience); + int level = ExpToLevel.get(expKey); + + if (level > MBServerStatics.LEVELCAP) + level = MBServerStatics.LEVELCAP; + + return level; + } + + // Get the base xp for a level + public static int getBaseExperience(int level) { + + if (level < LevelToExp.length) + return LevelToExp[level]; + + int fLevel = level - 1; + int baseXP = fLevel * fLevel * fLevel; + + return (int) ((fLevel < 40) ? (baseXP * 150) + : (baseXP * (150 + (7.6799998 * (level - 40))))); + } + + // Max XP granted for killing a blue, yellow or orange mob + public static float maxXPPerKill(int level) { + + if (level < 1) + level = 1; + + if (level > 75) + level = 75; + + return MaxExpPerLevel[level]; + } + + // Returns a penalty modifier depending on mob color + public static double getConMod(AbstractCharacter pc, AbstractCharacter mob) { + + switch (TargetColor.getCon(pc, mob)) { + case Red: + return 1.25; + case Orange: + return 1.15; + case Yellow: + return 1.05; + case Blue: + return 1; + case Cyan: + return 0.8; + case Green: + return 0.5; + default: + return 0; + } + } + + public static double getGroupMemberPenalty(double leadership, + PlayerCharacter currPlayer, ArrayList players, + int highestLevel) { + + double penalty = 0.0; + int level = currPlayer.getLevel(); + + // Group Size Penalty + + if (players.size() > 2) + penalty = (players.size() - 2) * 1.5; + + // Calculate Penalty For Highest level -> Current Player difference, != + // check to prevent divide by zero error + + if (highestLevel != level) + penalty += ((highestLevel - level) * .5); + + // double avgLevels = totalLevels / adjustedGroupSize; + // if (adjustedGroupSize >= 1) + // if (level < avgLevels) + // penalty += ((avgLevels - level) * .5); + + // Extra noob penalty + + if ((highestLevel - level) > 25) + penalty += (highestLevel - level - 25); + + return penalty; + } + + public static void doExperience(PlayerCharacter killer, AbstractCharacter mob, Group g) { + + // Check for some failure conditions + + if (killer == null || mob == null) + return; + + double grantedExperience = 0.0; + + if (g != null) { // Do group EXP stuff + + int leadership = 0; + int highestLevel = 0; + double penalty = 0.0; + + ArrayList giveEXPTo = new ArrayList<>(); + + // Check if leader is within range of kill and then get leadership + // skill + + Vector3fImmutable killLoc = mob.getLoc(); + + if (killLoc.distanceSquared2D(g.getGroupLead().getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) { + CharacterSkill leaderskill = g.getGroupLead().skills + .get("Leadership"); + + if (leaderskill != null) + leadership = leaderskill.getNumTrains(); + if (leadership > 90) + leadership = 90; // leadership caps at 90% + } + + // Check every group member for distance to see if they get xp + + for (PlayerCharacter pc : g.getMembers()) { + if (pc.isAlive()) { // Skip if the player is dead. + + // Check within range + + if (killLoc.distanceSquared2D(pc.getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) { + + giveEXPTo.add(pc); + + // Track highest level character + + if (pc.getLevel() > highestLevel) + highestLevel = pc.getLevel(); + } + } + } + + // Process every player in the group getting XP + + for (PlayerCharacter playerCharacter : giveEXPTo) { + if (playerCharacter.getLevel() >= MBServerStatics.LEVELCAP) + continue; + + // Sets Max XP with server exp mod taken into account. + + grantedExperience = (double) LOOTMANAGER.NORMAL_EXP_RATE * maxXPPerKill(playerCharacter.getLevel()); + + // Adjust XP for Mob Level + + grantedExperience *= getConMod(playerCharacter, mob); + + // Process XP for this member + + penalty = getGroupMemberPenalty(leadership, playerCharacter, giveEXPTo, + highestLevel); + + // Leadership Penalty Reduction + + if (leadership > 0) + penalty -= ((leadership) * 0.01) * penalty; + + // Modify for hotzone + + if (grantedExperience != 0) + if (ZoneManager.inHotZone(mob.getLoc())) + grantedExperience *= LOOTMANAGER.HOTZONE_EXP_RATE; + + // Check for 0 XP due to white mob, otherwise subtract penalty + // xp + + if (grantedExperience == 0) + grantedExperience = 1; + else { + grantedExperience -= (penalty * 0.01) * grantedExperience; + + // Errant Penalty Calculation + if (playerCharacter.getGuild().isEmptyGuild()) + grantedExperience *= 0.6; + } + + if (grantedExperience == 0) + grantedExperience = 1; + + // Grant the player the EXP + playerCharacter.grantXP((int) Math.floor(grantedExperience)); + } + + } else { // Give EXP to a single character + if (!killer.isAlive()) // Skip if the player is dead. + return; + + if (killer.getLevel() >= MBServerStatics.LEVELCAP) + return; + + // Get XP and adjust for Mob Level with world xp modifier taken into account + grantedExperience = (double) LOOTMANAGER.NORMAL_EXP_RATE * maxXPPerKill(killer.getLevel()); + grantedExperience *= getConMod(killer, mob); + + // Modify for hotzone + if (ZoneManager.inHotZone(mob.getLoc())) + grantedExperience *= LOOTMANAGER.HOTZONE_EXP_RATE; + + // Errant penalty + if (grantedExperience != 1) + if (killer.getGuild().isEmptyGuild()) + grantedExperience *= .6; + + // Grant XP + killer.grantXP((int) Math.floor(grantedExperience)); + } + } +} diff --git a/src/engine/objects/Formation.java b/src/engine/objects/Formation.java new file mode 100644 index 00000000..e8875764 --- /dev/null +++ b/src/engine/objects/Formation.java @@ -0,0 +1,118 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.math.Vector3f; + +public class Formation { + + // Offsets are as follows. + // X determines left/right offset + // Y not used + // Z determines front/back offset + + private static final Vector3f[] COLUMN = {new Vector3f(0, 0, 0), // Group + // Lead + new Vector3f(6, 0, 0), // Player 1 offset + new Vector3f(0, 0, -6), // Player 2 offset + new Vector3f(6, 0, -6), // Player 3 offset + new Vector3f(0, 0, -12), // Player 4 offset + new Vector3f(6, 0, -12), // Player 5 offset + new Vector3f(0, 0, -18), // Player 6 offset + new Vector3f(6, 0, -18), // Player 7 offset + new Vector3f(0, 0, -24), // Player 8 offset + new Vector3f(6, 0, -24)}; // Player 9 offset + + private static final Vector3f[] LINE = {new Vector3f(0, 0, 0), + new Vector3f(0, 0, -6), new Vector3f(0, 0, -12), + new Vector3f(0, 0, -18), new Vector3f(0, 0, -24), + new Vector3f(0, 0, -30), new Vector3f(0, 0, -36), + new Vector3f(0, 0, -42), new Vector3f(0, 0, -48), + new Vector3f(0, 0, -54)}; + + private static final Vector3f[] BOX = {new Vector3f(0, 0, 0), + new Vector3f(-6, 0, 0), new Vector3f(6, 0, 0), + new Vector3f(-6, 0, -6), new Vector3f(0, 0, -6), + new Vector3f(6, 0, -6), new Vector3f(-6, 0, -12), + new Vector3f(0, 0, -12), new Vector3f(5, 0, -12), + new Vector3f(0, 0, -18)}; + + private static final Vector3f[] TRIANGLE = {new Vector3f(0, 0, 0), + new Vector3f(-6, 0, -6), new Vector3f(6, 0, -6), + new Vector3f(-12, 0, -12), new Vector3f(0, 0, -12), + new Vector3f(12, 0, -12), new Vector3f(-18, 0, -18), + new Vector3f(-6, 0, -18), new Vector3f(6, 0, -18), + new Vector3f(18, 0, -18)}; + + private static final Vector3f[] CIRCLE = {new Vector3f(0, 0, 0), + new Vector3f(-12, 0, -3), new Vector3f(12, 0, -3), + new Vector3f(-18, 0, -12), new Vector3f(18, 0, -12), + new Vector3f(-18, 0, -21), new Vector3f(18, 0, -21), + new Vector3f(-12, 0, -30), new Vector3f(12, 0, -30), + new Vector3f(0, 0, -33)}; + + private static final Vector3f[] RANKS = {new Vector3f(0, 0, 0), + new Vector3f(0, 0, -6), new Vector3f(-6, 0, 0), + new Vector3f(-6, 0, -6), new Vector3f(6, 0, 0), + new Vector3f(6, 0, -6), new Vector3f(-12, 0, 0), + new Vector3f(-12, 0, -6), new Vector3f(12, 0, 0), + new Vector3f(12, 0, -6)}; + + private static final Vector3f[] WEDGE = {new Vector3f(0, 0, 0), + new Vector3f(6, 0, 0), new Vector3f(-6, 0, -6), + new Vector3f(12, 0, -6), new Vector3f(-12, 0, -12), + new Vector3f(18, 0, -12), new Vector3f(-18, 0, -18), + new Vector3f(24, 0, -18), new Vector3f(-24, 0, -24), + new Vector3f(30, 0, -24)}; + + private static final Vector3f[] INVERSEWEDGE = {new Vector3f(0, 0, 0), + new Vector3f(6, 0, 0), new Vector3f(-6, 0, 6), + new Vector3f(12, 0, 6), new Vector3f(-12, 0, 12), + new Vector3f(18, 0, 12), new Vector3f(-18, 0, 18), + new Vector3f(24, 0, 18), new Vector3f(-24, 0, 24), + new Vector3f(30, 0, 24)}; + + private static final Vector3f[] T = {new Vector3f(0, 0, 0), + new Vector3f(-6, 0, 0), new Vector3f(6, 0, 0), + new Vector3f(0, 0, -6), new Vector3f(-12, 0, 0), + new Vector3f(12, 0, 0), new Vector3f(0, 0, -12), + new Vector3f(-18, 0, 0), new Vector3f(18, 0, 0), + new Vector3f(0, 0, -18)}; + + public static Vector3f getOffset(int formation, int position) { + if (position > 9 || position < 0) { + // TODO log error here + position = 0; + } + + switch (formation) { + case 0: + return Formation.COLUMN[position]; + case 1: + return Formation.LINE[position]; + case 2: + return Formation.BOX[position]; + case 3: + return Formation.TRIANGLE[position]; + case 4: + return Formation.CIRCLE[position]; + case 5: + return Formation.RANKS[position]; + case 6: + return Formation.WEDGE[position]; + case 7: + return Formation.INVERSEWEDGE[position]; + case 9: + return Formation.T[position]; + default: // default to box + return Formation.BOX[position]; + } + } +} diff --git a/src/engine/objects/Group.java b/src/engine/objects/Group.java new file mode 100644 index 00000000..2dbf310a --- /dev/null +++ b/src/engine/objects/Group.java @@ -0,0 +1,182 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.GroupManager; +import engine.job.JobScheduler; +import engine.jobs.UpdateGroupJob; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.group.GroupUpdateMsg; +import engine.server.MBServerStatics; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + + +public class Group extends AbstractWorldObject { + + public final Set members; + private PlayerCharacter groupLead; + private boolean splitGold = true; + private int formation = 2; + + private UpdateGroupJob updateGroupJob = null; + + /** + * No Id Constructor + */ + public Group(PlayerCharacter pc) { + super(); + this.groupLead = pc; + this.members = Collections.newSetFromMap(new ConcurrentHashMap<>()); + } + + /** + * Normal Constructor + */ + public Group(PlayerCharacter pc, int newUUID) { + super(newUUID); + this.groupLead = pc; + this.members = Collections.newSetFromMap(new ConcurrentHashMap<>()); + } + + public static boolean sameGroup(PlayerCharacter a, PlayerCharacter b) { + + if (a == null || b == null) + return false; + + Group aG = GroupManager.getGroup(a); + Group bG = GroupManager.getGroup(b); + + if (aG == null || bG == null) + return false; + + return aG.getObjectUUID() == bG.getObjectUUID(); + + } + + /* + * Getters + */ + public PlayerCharacter getGroupLead() { + return this.groupLead; + } + + public Set getMembers() { + return this.members; + } + + public boolean getSplitGold() { + return this.splitGold; + } + + public void setSplitGold(boolean value) { + this.splitGold = value; + } + + public int getFormation() { + return this.formation; + } + + /* + * Setters + */ + public void setFormation(int value) { + if (value < 0 || value > 8) + value = 2; // Default Box + this.formation = value; + } + + public String getFormationName() { + return MBServerStatics.FORMATION_NAMES[this.formation]; + } + + public boolean setGroupLead(int ID) { + for (PlayerCharacter pc : this.members) { + if (pc.getObjectUUID() == ID) { + this.groupLead = pc; + return true; + } + } + return false; + } + + /* + * Utils + */ + public boolean isGroupLead(int ID) { + return (this.groupLead.getObjectUUID() == ID); + } + + public boolean isGroupLead(PlayerCharacter pc) { + if (pc == null || this.groupLead == null) + return false; + return (this.groupLead.getObjectUUID() == pc.getObjectUUID()); + } + + public boolean toggleSplitGold() { + this.splitGold = this.splitGold == false; + return this.splitGold; + } + + public void sendUpdate(GroupUpdateMsg msg) { + + for (PlayerCharacter pc : this.members) { + Dispatch dispatch = Dispatch.borrow(pc, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } + } + + public boolean addGroupMember(PlayerCharacter pc) { + + if (this.members.size() > 9) // group full + return false; + + if (this.members.contains(pc)) // Can't add player twice + return false; + + this.members.add(pc); + return true; + } + + public int removeGroupMember(PlayerCharacter pc) { + + this.members.remove(pc); // remove player + return this.members.size(); + } + + public void clearMembers() { + this.members.clear(); + } + + public void addUpdateGroupJob() { + this.updateGroupJob = new UpdateGroupJob(this); + JobScheduler.getInstance().scheduleJob(this.updateGroupJob, MBServerStatics.UPDATE_GROUP_RATE); + } + + public void removeUpdateGroupJob() { + this.updateGroupJob.cancelJob(); + this.updateGroupJob = null; + } + + /* + * Database + */ + @Override + public void updateDatabase() { + // TODO Create update logic. + } + + @Override + public void runAfterLoad() { + } +} diff --git a/src/engine/objects/Guild.java b/src/engine/objects/Guild.java new file mode 100644 index 00000000..154e286a --- /dev/null +++ b/src/engine/objects/Guild.java @@ -0,0 +1,1262 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.*; +import engine.db.archive.DataWarehouse; +import engine.db.archive.GuildRecord; +import engine.db.handlers.dbGuildHandler; +import engine.gameManager.*; +import engine.net.ByteBufferWriter; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.AllianceChangeMsg; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.UpdateClientAlliancesMsg; +import engine.net.client.msg.guild.GuildInfoMsg; +import engine.server.world.WorldServer; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + +public class Guild extends AbstractWorldObject { + + private static Guild errantGuild; + private static Guild errantNation; + private final String name; + private final GuildTag guildTag; + private final int charter; + private final String leadershipType; // Have to see how this is sent to the client + private final int repledgeMin; + private final int repledgeMax; + private final int repledgeKick; + private final int teleportMin; + private final int teleportMax; + private final ConcurrentHashMap guildCondemned = new ConcurrentHashMap<>(); + public boolean wooWasModified; + public HashMap guildAlliances = new HashMap<>(); + private Guild nation; + // TODO add these to database + private String motto = ""; + private String motd = ""; + private String icmotd = ""; + private String nmotd = ""; + private int guildLeaderUUID; + private int realmsOwned; + private int cityUUID = 0; + private int mineTime; + private ArrayList banishList; + private ArrayList characterKOSList; + private ArrayList guildKOSList; + private ArrayList allyList = new ArrayList<>(); + private ArrayList enemyList = new ArrayList<>(); + private ArrayList recommendList = new ArrayList<>(); + private ArrayList subGuildList; + private int nationUUID = 0; + private GuildState guildState = GuildState.Errant; + private String hash; + private boolean ownerIsNPC; + + /** + * No Id Constructor + */ + public Guild(String name, Guild nat, int charter, + String leadershipType, GuildTag gt, String motto) { + super(); + this.name = name; + this.nation = nat; + this.charter = charter; + this.realmsOwned = 0; + this.leadershipType = leadershipType; + + this.banishList = new ArrayList<>(); + this.characterKOSList = new ArrayList<>(); + this.guildKOSList = new ArrayList<>(); + this.allyList = new ArrayList<>(); + this.enemyList = new ArrayList<>(); + this.subGuildList = new ArrayList<>(); + + this.guildTag = gt; + + //set for player city + this.repledgeMin = 1; + this.repledgeMax = 100; + this.repledgeKick = 100; + this.teleportMin = 1; + this.teleportMax = 100; + this.mineTime = 0; + this.motto = motto; + } + + /** + * Normal Constructor + */ + public Guild(String name, Guild nat, int charter, + String leadershipType, GuildTag gt, int newUUID) { + super(newUUID); + this.name = name; + this.nation = nat; + + this.charter = charter; + this.realmsOwned = 0; + this.leadershipType = leadershipType; + + this.banishList = new ArrayList<>(); + this.characterKOSList = new ArrayList<>(); + this.guildKOSList = new ArrayList<>(); + this.allyList = new ArrayList<>(); + this.enemyList = new ArrayList<>(); + this.subGuildList = new ArrayList<>(); + this.guildTag = gt; + + //set for player city + this.repledgeMin = 1; + this.repledgeMax = 100; + this.repledgeKick = 100; + this.teleportMin = 1; + this.teleportMax = 100; + this.mineTime = 0; + this.hash = "ERRANT"; + } + + /** + * ResultSet Constructor + */ + public Guild(ResultSet rs) throws SQLException { + super(rs); + DbObjectType objectType; + + this.name = rs.getString("name"); + this.charter = rs.getInt("charter"); + this.leadershipType = rs.getString("leadershipType"); + + this.guildTag = new GuildTag(rs.getInt("backgroundColor01"), + rs.getInt("backgroundColor02"), + rs.getInt("symbolColor"), + rs.getInt("symbol"), + rs.getInt("backgroundDesign")); + + //Declare Nations and Subguilds + this.nationUUID = rs.getInt("parent"); + this.cityUUID = rs.getInt("ownedCity"); + this.guildLeaderUUID = rs.getInt("leaderUID"); + this.motto = rs.getString("motto"); + this.motd = rs.getString("motd"); + this.icmotd = rs.getString("icMotd"); + this.nmotd = rs.getString("nationMotd"); + + this.repledgeMin = rs.getInt("repledgeMin"); + this.repledgeMax = rs.getInt("repledgeMax"); + this.repledgeKick = rs.getInt("repledgeKick"); + this.teleportMin = rs.getInt("teleportMin"); + this.teleportMax = rs.getInt("teleportMax"); + + this.mineTime = rs.getInt("mineTime"); + this.wooWasModified = false; + + this.hash = rs.getString("hash"); + } + + public static AbstractCharacter GetGL(Guild guild) { + if (guild == null) + return null; + + if (guild.guildLeaderUUID == 0) + return null; + + if (guild.ownerIsNPC) + return NPC.getFromCache(guild.guildLeaderUUID); + + return PlayerCharacter.getFromCache(guild.guildLeaderUUID); + } + + public static boolean sameGuild(Guild a, Guild b) { + if (a == null || b == null) + return false; + return a.getObjectUUID() == b.getObjectUUID(); + } + + public static boolean sameGuildExcludeErrant(Guild a, Guild b) { + if (a == null || b == null) + return false; + if (a.isEmptyGuild() || b.isEmptyGuild()) + return false; + return a.getObjectUUID() == b.getObjectUUID(); + } + + public static boolean sameGuildIncludeErrant(Guild a, Guild b) { + if (a == null || b == null) + return false; + if (a.isEmptyGuild() || b.isEmptyGuild()) + return true; + return a.getObjectUUID() == b.getObjectUUID(); + } + + public static boolean sameNation(Guild a, Guild b) { + if (a == null || b == null) + return false; + if (a.getObjectUUID() == b.getObjectUUID()) + return true; + if (a.nation == null || b.nation == null) + return false; + return a.nation.getObjectUUID() == b.nation.getObjectUUID(); + } + + public static boolean sameNationExcludeErrant(Guild a, Guild b) { + if (a == null || b == null) + return false; + if (a.getObjectUUID() == b.getObjectUUID()) + return true; + if (a.nation == null || b.nation == null) + return false; + return a.nation.getObjectUUID() == b.nation.getObjectUUID() && !a.nation.isEmptyGuild(); + } + + public static boolean isTaxCollector(int uuid) { + //TODO add the handling for this later + return false; + } + + public static boolean canSwearIn(Guild toSub) { + + boolean canSwear = false; + + switch (toSub.guildState) { + + case Protectorate: + case Petitioner: + canSwear = true; + break; + default: + canSwear = false; + } + + return canSwear; + } + + public static void _serializeForClientMsg(Guild guild, ByteBufferWriter writer) { + Guild.serializeForClientMsg(guild, writer, null, false); + } + + public static void serializeForClientMsg(Guild guild, ByteBufferWriter writer, PlayerCharacter pc, boolean reshowGuild) { + writer.putInt(guild.getObjectType().ordinal()); + writer.putInt(guild.getObjectUUID()); + writer.putInt(guild.nation.getObjectType().ordinal()); + writer.putInt(guild.nation.getObjectUUID()); + + if (pc == null) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); // Defaults + writer.putInt(0); // Defaults + } else { + writer.putString(guild.name); + writer.putString(guild.nation.name); + writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus())); // TODO Double check this is + // title and rank + if (GuildStatusController.isGuildLeader(pc.getGuildStatus())) + writer.putInt(PlayerCharacter.GetPlayerRealmTitle(pc)); + else + writer.putInt(GuildStatusController.getRank(pc.getGuildStatus())); + //writer.putInt(GuildStatusController.getRank(pc.getGuildStatus())); + } + + City ownedCity = guild.getOwnedCity(); + + if (ownedCity != null) { + Realm realm = guild.getOwnedCity().getRealm(); + if (realm != null && realm.getRulingCity() != null) { + if (realm.getRulingCity().equals(ownedCity)) { + writer.putInt(realm.getCharterType()); + } else + writer.putInt(0); + } else { + writer.putInt(0); + } + } else + writer.putInt(0); + + writer.putFloat(200); + writer.putFloat(200); // Pad + + GuildTag._serializeForDisplay(guild.guildTag, writer); + GuildTag._serializeForDisplay(guild.nation.guildTag, writer); + if (reshowGuild) { + writer.putInt(1); + writer.putInt(guild.getObjectType().ordinal()); + writer.putInt(guild.getObjectUUID()); + + } else + writer.putInt(0); // Pad + } + + public static void serializeForTrack(Guild guild, ByteBufferWriter writer) { + Guild.serializeGuildForTrack(guild, writer); + if (guild.nation != null) + Guild.serializeGuildForTrack(guild.nation, writer); + else + Guild.addErrantForTrack(writer); + } + + public static void serializeGuildForTrack(Guild guild, ByteBufferWriter writer) { + writer.putInt(guild.getObjectType().ordinal()); + writer.putInt(guild.getObjectUUID()); + writer.put((byte) 1); + GuildTag._serializeForDisplay(guild.guildTag, writer); + } + + public static void serializeErrantForTrack(ByteBufferWriter writer) { + addErrantForTrack(writer); //Guild + addErrantForTrack(writer); //Nation + } + + private static void addErrantForTrack(ByteBufferWriter writer) { + writer.putInt(0); //type + writer.putInt(0); //ID + writer.put((byte) 1); + writer.putInt(16); //Tags + writer.putInt(16); + writer.putInt(16); + writer.putInt(0); + writer.putInt(0); + } + + public static Guild getErrantGuild() { + + if (Guild.errantGuild == null) + Guild.errantGuild = new Guild("None", Guild.getErrantNation(), 0, + "Anarchy", GuildTag.ERRANT, 0); + + return Guild.errantGuild; + } + + public static Guild getErrantNation() { + if (Guild.errantNation == null) + Guild.errantNation = new Guild("None", null, 10, "Despot Rule", GuildTag.ERRANT, 0); + return Guild.errantNation; + } + + public static Guild getGuild(final int objectUUID) { + + if (objectUUID == 0) + return Guild.getErrantGuild(); + Guild guild = (Guild) DbManager.getFromCache(Enum.GameObjectType.Guild, objectUUID); + if (guild != null) + return guild; + + Guild dbGuild = DbManager.GuildQueries.GET_GUILD(objectUUID); + + if (dbGuild == null) + return Guild.getErrantGuild(); + else + return dbGuild; + } + + public static void UpdateClientAlliances(Guild toUpdate) { + UpdateClientAlliancesMsg ucam = new UpdateClientAlliancesMsg(toUpdate); + + + for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) { + + if (Guild.sameGuild(player.getGuild(), toUpdate)) { + Dispatch dispatch = Dispatch.borrow(player, ucam); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + + } + } + + public static void UpdateClientAlliancesForPlayer(PlayerCharacter toUpdate) { + UpdateClientAlliancesMsg ucam = new UpdateClientAlliancesMsg(toUpdate.getGuild()); + Dispatch dispatch = Dispatch.borrow(toUpdate, ucam); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + + } + + public static Guild getFromCache(int id) { + return (Guild) DbManager.getFromCache(GameObjectType.Guild, id); + } + + public static ArrayList GuildRoster(Guild guild) { + ArrayList roster = new ArrayList<>(); + if (guild == null) + return roster; + + if (guild.isEmptyGuild()) + return roster; + + if (DbManager.getList(GameObjectType.PlayerCharacter) == null) + return roster; + for (AbstractGameObject ago : DbManager.getList(GameObjectType.PlayerCharacter)) { + PlayerCharacter toAdd = (PlayerCharacter) ago; + + if (!toAdd.getGuild().equals(guild)) + continue; + + if (toAdd.isDeleted()) + continue; + + roster.add(toAdd); + + } + return roster; + } + + /* + * Getters + */ + @Override + public String getName() { + return name; + } + + public String getLeadershipType() { + return leadershipType; + } + + public Guild getNation() { + + if (this.nation == null) + return Guild.getErrantGuild(); + return this.nation; + } + + public boolean isNation() { + return this.nation != null && this.cityUUID != 0 && this.nation == this; + } + + public void setNation(Guild nation) { + if (nation == null) + this.nation = Guild.getErrantGuild(); + else + this.nation = nation; + } + + public City getOwnedCity() { + + return City.getCity(this.cityUUID); + } + + public void setCityUUID(int cityUUID) { + this.cityUUID = cityUUID; + } + + public ArrayList getBanishList() { + if (banishList == null) + return new ArrayList<>(); + return banishList; + } + + public ArrayList getCharacterKOSList() { + return characterKOSList; + } + + public ArrayList getGuildKOSList() { + return guildKOSList; + } + + public ArrayList getAllyList() { + return allyList; + } + + public ArrayList getEnemyList() { + return enemyList; + } + + public ArrayList getSubGuildList() { + + return subGuildList; + } + + public GuildTag getGuildTag() { + return this.guildTag; + } + + public int getCharter() { + return charter; + } + + /* + * Utils + */ + + public int getGuildLeaderUUID() { + return this.guildLeaderUUID; + } + + /* + * Setters + */ + public void setGuildLeaderUUID(int value) { + this.guildLeaderUUID = value; + } + + public String getMOTD() { + return this.motd; + } + + public void setMOTD(String value) { + this.motd = value; + } + + public String getICMOTD() { + return this.icmotd; + } + + public void setICMOTD(String value) { + this.icmotd = value; + } + + public boolean isNPCGuild() { + + return this.ownerIsNPC; + } + + public int getRepledgeMin() { + return this.repledgeMin; + } + + public int getRepledgeMax() { + return this.repledgeMax; + } + + public int getRepledgeKick() { + return this.repledgeKick; + } + + public int getTeleportMin() { + return this.teleportMin; + } + + public int getTeleportMax() { + return this.teleportMax; + } + + public int getMineTime() { + return this.mineTime; + } + + /* + * Serialization + */ + + public void setMineTime(int mineTime) { + this.mineTime = mineTime; + } + + public boolean setGuildLeader(AbstractCharacter ac) { + if (ac == null) + return false; + // errant guilds cant be guild leader. + if (this.isEmptyGuild()) + return false; + + if (!DbManager.GuildQueries.SET_GUILD_LEADER(ac.getObjectUUID(), this.getObjectUUID())) { + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + ChatManager.chatGuildError((PlayerCharacter) ac, "Failed to change guild leader!"); + return false; + } + + PlayerCharacter oldGuildLeader = PlayerCharacter.getFromCache(this.guildLeaderUUID); + + //old guildLeader no longer has guildLeadership stauts. + if (oldGuildLeader != null) + oldGuildLeader.setGuildLeader(false); + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + ((PlayerCharacter) ac).setGuildLeader(true); + this.guildLeaderUUID = ac.getObjectUUID(); + + return true; + } + + public boolean setGuildLeaderForCreate(AbstractCharacter ac) { + if (ac == null) + return false; + // errant guilds cant be guild leader. + if (this.isEmptyGuild()) + return false; + + if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + ((PlayerCharacter) ac).setGuildLeader(true); + this.guildLeaderUUID = ac.getObjectUUID(); + + return true; + } + + public int getBgc1() { + if (this.guildTag != null) + return this.guildTag.backgroundColor01; + return 16; + } + + public int getBgc2() { + if (this.guildTag != null) + return this.guildTag.backgroundColor02; + else + return 16; + } + + public int getBgDesign() { + if (this.guildTag != null) + return this.guildTag.backgroundDesign; + return 0; + } + + public int getSc() { + if (this.guildTag != null) + return this.guildTag.symbolColor; + return 16; + } + + public int getSymbol() { + if (this.guildTag != null) + return this.guildTag.symbol; + return 0; + } + + public boolean isEmptyGuild() { + return this.getObjectUUID() == Guild.errantGuild.getObjectUUID(); + + } + + public boolean isGuildLeader(int uuid) { + + return (this.guildLeaderUUID == uuid); + } + + /** + * Removes a PlayerCharacter from this (non-Errant) Guild. + * + * @param pc PlayerCharacter to be removed + */ + public void removePlayer(PlayerCharacter pc, GuildHistoryType historyType) { + + if (this.isEmptyGuild()) { + Logger.warn("Attempted to remove a PlayerCharacter (" + pc.getObjectUUID() + ") from an errant guild."); + return; + } + + //Add to Guild History + if (pc.getGuild() != null) { + if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(pc.getGuildUUID(), pc, DateTime.now(), historyType)) { + GuildHistory guildHistory = new GuildHistory(pc.getGuildUUID(), pc.getGuild().name, DateTime.now(), historyType); + pc.getGuildHistory().add(guildHistory); + } + } + + // Clear Guild Ranks + pc.resetGuildStatuses(); + pc.setGuild(Guild.getErrantGuild()); + + pc.incVer(); + DispatchMessage.sendToAllInRange(pc, new GuildInfoMsg(pc, Guild.getErrantGuild(), 2)); + + } + + public void upgradeGuildState(boolean nation) { + if (nation) { + this.guildState = GuildState.Nation; + return; + } + switch (this.guildState) { + + case Errant: + this.guildState = GuildState.Petitioner; + break; + case Sworn: + //Can't upgrade + break; + case Protectorate: + this.guildState = GuildState.Province; + break; + case Petitioner: + this.guildState = GuildState.Sworn; + break; + case Province: + //Can't upgrade + break; + case Nation: + //Can't upgrade + break; + case Sovereign: + this.guildState = GuildState.Protectorate; + break; + } + + } + + public void downgradeGuildState() { + + switch (this.guildState) { + case Errant: + break; + case Sworn: + this.guildState = GuildState.Errant; + break; + case Protectorate: + this.guildState = GuildState.Sovereign; + break; + case Petitioner: + this.guildState = GuildState.Errant; + break; + case Province: + this.guildState = GuildState.Sovereign; + break; + case Nation: + this.guildState = GuildState.Sovereign; + break; + case Sovereign: + this.guildState = GuildState.Errant; + break; + } + + } + + public boolean canSubAGuild(Guild toSub) { + + boolean canSub; + + if (this.equals(toSub)) + return false; + switch (this.guildState) { + case Nation: + case Sovereign: + canSub = true; + break; + default: + canSub = false; + } + + switch (toSub.guildState) { + case Errant: + case Sovereign: + canSub = true; + break; + default: + canSub = false; + } + City nationCap = City.getCity(nation.cityUUID); + if (nation.getSubGuildList().size() >= nationCap.getRank()) { + canSub = false; + } + return canSub; + } + + public int getRealmsOwnedFlag() { + int flag = 0; + switch (realmsOwned) { + case 0: + flag = 0; + case 1: + case 2: + flag = 1; + break; + case 3: + case 4: + flag = 2; + break; + case 5: + flag = 3; + break; + default: + flag = 3; + break; + } + return flag; + } + + public void serializeForPlayer(ByteBufferWriter writer) { + writer.putInt(this.getObjectType().ordinal()); + writer.putInt(this.getObjectUUID()); + writer.putInt(this.nation.getObjectType().ordinal()); + writer.putInt(this.nation.getObjectUUID()); + + } + + @Override + public void updateDatabase() { + DbManager.GuildQueries.updateDatabase(this); + } + + public boolean isRealmRuler() { + + City ownedCity; + Building tol; + + ownedCity = this.getOwnedCity(); + + if (ownedCity == null) + return false; + + tol = ownedCity.getTOL(); + + if (tol == null) + return false; + + return tol.getRank() == 8; + + } + + @Override + public void runAfterLoad() { + + try { + DbObjectType objectType = DbManager.BuildingQueries.GET_UID_ENUM(this.guildLeaderUUID); + this.ownerIsNPC = (objectType == DbObjectType.NPC); + } catch (Exception e) { + this.ownerIsNPC = false; + Logger.error("Failed to find Object Type for owner " + this.guildLeaderUUID); + } + + + //LOad Owners in Cache so we do not have to continuely look in the db for owner. + if (this.ownerIsNPC) { + if (NPC.getNPC(this.guildLeaderUUID) == null) + Logger.info("Guild UID " + this.getObjectUUID() + " Failed to Load NPC Owner with ID " + this.guildLeaderUUID); + + } else if (this.guildLeaderUUID != 0) { + if (PlayerCharacter.getPlayerCharacter(this.guildLeaderUUID) == null) + Logger.info("Guild UID " + this.getObjectUUID() + " Failed to Load Player Owner with ID " + this.guildLeaderUUID); + } + + // If loading this guild for the first time write it's character record to disk + + if (ConfigManager.serverType.equals(ServerType.WORLDSERVER) + && (hash == null)) { + + this.setHash(); + + if (DataWarehouse.recordExists(Enum.DataRecordType.GUILD, this.getObjectUUID()) == false) { + GuildRecord guildRecord = GuildRecord.borrow(this, Enum.RecordEventType.CREATE); + DataWarehouse.pushToWarehouse(guildRecord); + } + + } + + if (WorldServer.worldUUID == nationUUID && this.cityUUID != 0) + this.nation = this; + else if (nationUUID == 0 || (WorldServer.worldUUID == nationUUID && this.cityUUID == 0)) { + this.nation = Guild.getErrantGuild(); + this.nmotd = ""; + } else + this.nation = Guild.getGuild(nationUUID); + + if (this.nation == null) + this.nation = Guild.getErrantGuild(); + //Get guild states. + try { + this.subGuildList = DbManager.GuildQueries.GET_SUB_GUILDS(this.getObjectUUID()); + } catch (Exception e) { + + this.subGuildList = new ArrayList<>(); + Logger.error("FAILED TO LOAD SUB GUILDS FOR UUID " + this.getObjectUUID()); + } + + if (this.nation == this && subGuildList.size() > 0) + this.guildState = GuildState.Nation; + else if (this.nation.equals(this)) + this.guildState = GuildState.Sovereign; + else if (!this.nation.isEmptyGuild() && this.cityUUID != 0) + this.guildState = GuildState.Province; + else if (!this.nation.isEmptyGuild()) + this.guildState = GuildState.Sworn; + else + this.guildState = GuildState.Errant; + + if (this.cityUUID == 0) + return; + + + // Calculate number of realms this guild controls + // Only do this on the game server to avoid loading a TOL/City/Zone needlessly + + if ((ConfigManager.serverType.equals(ServerType.WORLDSERVER)) && + (this.isRealmRuler() == true)) { + this.realmsOwned++; + if (!this.nation.equals(this)) { + this.nation.realmsOwned++; + } + } + + if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)) { + + //add alliance list, clear all lists as there seems to be a bug where alliances are doubled, need to find where. + //possible runAfterLoad being called twice?!?! + this.banishList = dbGuildHandler.GET_GUILD_BANISHED(this.getObjectUUID()); + this.characterKOSList = DbManager.GuildQueries.GET_GUILD_KOS_CHARACTER(this.getObjectUUID()); + this.guildKOSList = DbManager.GuildQueries.GET_GUILD_KOS_GUILD(this.getObjectUUID()); + + this.allyList.clear(); + this.enemyList.clear(); + this.recommendList.clear(); + + try { + DbManager.GuildQueries.LOAD_ALL_ALLIANCES_FOR_GUILD(this); + for (GuildAlliances guildAlliance : this.guildAlliances.values()) { + if (guildAlliance.isRecommended()) { + Guild recommendedGuild = Guild.getGuild(guildAlliance.getAllianceGuild()); + if (recommendedGuild != null) + this.recommendList.add(recommendedGuild); + } else if (guildAlliance.isAlly()) { + Guild alliedGuild = Guild.getGuild(guildAlliance.getAllianceGuild()); + if (alliedGuild != null) + this.allyList.add(alliedGuild); + } else { + Guild enemyGuild = Guild.getGuild(guildAlliance.getAllianceGuild()); + if (enemyGuild != null) + this.enemyList.add(enemyGuild); + } + + } + } catch (Exception e) { + Logger.error(this.getObjectUUID() + e.getMessage()); + } + } + } + + /** + * @return the motto + */ + public String getMotto() { + return motto; + } + + public GuildState getGuildState() { + return guildState; + } + + public void setGuildState(GuildState guildState) { + this.guildState = guildState; + } + + /** + * @return the realmsOwned + */ + public int getRealmsOwned() { + return realmsOwned; + } + + /** + * @param realmsOwned the realmsOwned to set + */ + public void setRealmsOwned(int realmsOwned) { + this.realmsOwned = realmsOwned; + } + + public void removeSubGuild(Guild subGuild) { + + // Update database + + if (!DbManager.GuildQueries.UPDATE_PARENT(subGuild.getObjectUUID(), WorldServer.worldUUID)) + Logger.debug("Failed to set Nation Guild for Guild with UID " + subGuild.getObjectUUID()); + + // Guild without any subs is no longer a nation + + if (subGuild.getOwnedCity() == null) { + subGuild.nation = null; + } else { + subGuild.nation = subGuild; + } + + // Downgrade guild + + subGuild.downgradeGuildState(); + + // Remove from collection + + subGuildList.remove(subGuild); + + GuildManager.updateAllGuildTags(subGuild); + GuildManager.updateAllGuildBinds(subGuild, subGuild.getOwnedCity()); + + } + + public ConcurrentHashMap getGuildCondemned() { + return guildCondemned; + } + + public String getHash() { + return hash; + } + + public void setHash() { + this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID()); + + DataWarehouse.writeHash(Enum.DataRecordType.GUILD, this.getObjectUUID()); + } + + public Enum.GuildType getGuildType() { + try { + return Enum.GuildType.values()[this.charter]; + } catch (Exception e) { + Logger.error(e); + return Enum.GuildType.NONE; + } + + } + + public ArrayList getRecommendList() { + return recommendList; + } + + public void setRecommendList(ArrayList recommendList) { + this.recommendList = recommendList; + } + + public synchronized boolean addGuildToAlliance(AllianceChangeMsg msg, final AllianceType allianceType, Guild toGuild, PlayerCharacter player) { + + Dispatch dispatch; + + // Member variable assignment + + + if (toGuild == null) + return false; + + if (!Guild.sameGuild(player.getGuild(), this)) { + msg.setMsgType(AllianceChangeMsg.ERROR_NOT_SAME_GUILD); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (allianceType == AllianceType.Ally || allianceType == AllianceType.Enemy) + if (!GuildStatusController.isInnerCouncil(player.getGuildStatus()) && !GuildStatusController.isGuildLeader(player.getGuildStatus())) { + msg.setMsgType(AllianceChangeMsg.ERROR_NOT_AUTHORIZED); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (allianceType == AllianceType.RecommendedAlly || allianceType == AllianceType.RecommendedEnemy) { + if (!GuildStatusController.isFullMember(player.getGuildStatus())) { + msg.setMsgType(AllianceChangeMsg.ERROR_NOT_AUTHORIZED); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + } + + // if (this.getGuildType() != toGuild.getGuildType()){ + // msg.setMsgType(AllianceChangeMsg.ERROR_NOT_SAME_FACTION); + // dispatch = Dispatch.borrow(player, msg); + // DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + // return false; + // } + + + switch (allianceType) { + case RecommendedAlly: + if (recommendList.size() == 10) { + msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (recommendList.contains(toGuild)) { + ErrorPopupMsg.sendErrorMsg(player, "This guild is already recommonded!"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (!DbManager.GuildQueries.ADD_TO_ALLIANCE_LIST(this.getObjectUUID(), toGuild.getObjectUUID(), true, true, player.getFirstName())) { + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + return false; + } + + GuildAlliances guildAlliance = new GuildAlliances(this.getObjectUUID(), toGuild.getObjectUUID(), true, true, player.getFirstName()); + this.guildAlliances.put(toGuild.getObjectUUID(), guildAlliance); + this.removeGuildFromEnemy(toGuild); + this.removeGuildFromAlliance(toGuild); + this.recommendList.add(toGuild); + + + return true; + + case RecommendedEnemy: + if (recommendList.size() == 10) { + msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (recommendList.contains(toGuild)) { + ErrorPopupMsg.sendErrorMsg(player, "This guild is already recommonded!"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (!DbManager.GuildQueries.ADD_TO_ALLIANCE_LIST(this.getObjectUUID(), toGuild.getObjectUUID(), true, false, player.getFirstName())) { + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + return false; + } + + GuildAlliances enemyAlliance = new GuildAlliances(this.getObjectUUID(), toGuild.getObjectUUID(), true, false, player.getFirstName()); + this.guildAlliances.put(toGuild.getObjectUUID(), enemyAlliance); + this.removeGuildFromEnemy(toGuild); + this.removeGuildFromAlliance(toGuild); + this.recommendList.add(toGuild); + + return true; + + case Ally: + if (allyList.size() == 10) { + msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (allyList.contains(toGuild)) { + ErrorPopupMsg.sendErrorMsg(player, "This guild is already an Ally!"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (!this.guildAlliances.containsKey(toGuild.getObjectUUID())) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!"); + Logger.error(this.getObjectUUID() + " Could not find alliance Guild"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + GuildAlliances ally = this.guildAlliances.get(toGuild.getObjectUUID()); + if (!ally.UpdateAlliance(AllianceType.Ally, this.recommendList.contains(toGuild))) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!"); + Logger.error(this.getObjectUUID() + " failed to update alliance Database"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + + } + + this.removeGuildFromEnemy(toGuild); + this.removeGuildFromRecommended(toGuild); + + this.allyList.add(toGuild); + Guild.UpdateClientAlliances(this); + + + break; + case Enemy: + if (enemyList.size() == 10) { + msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (enemyList.contains(toGuild)) { + ErrorPopupMsg.sendErrorMsg(player, "This guild is already an Enemy!"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + if (!this.guildAlliances.containsKey(toGuild.getObjectUUID())) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!"); + Logger.error(this.getObjectUUID() + " Could not find alliance Guild"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + + GuildAlliances enemy = this.guildAlliances.get(toGuild.getObjectUUID()); + if (!enemy.UpdateAlliance(AllianceType.Enemy, this.recommendList.contains(toGuild))) { + ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!"); + Logger.error(this.getObjectUUID() + " failed to update alliance Database"); + msg.setMsgType((byte) 15); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + + } + + //remove from other allied lists. + this.removeGuildFromAlliance(toGuild); + this.removeGuildFromRecommended(toGuild); + + this.enemyList.add(toGuild); + + Guild.UpdateClientAlliances(this); + break; + } + + // once here everything passed, send successMsg; + msg.setMsgType(AllianceChangeMsg.INFO_SUCCESS); + dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return true; + } + + public synchronized boolean removeGuildFromAlliance(Guild toRemove) { + this.allyList.remove(toRemove); + return true; + } + + public synchronized boolean removeGuildFromEnemy(Guild toRemove) { + this.enemyList.remove(toRemove); + return true; + } + + public synchronized boolean removeGuildFromRecommended(Guild toRemove) { + this.recommendList.remove(toRemove); + return true; + } + + public synchronized boolean removeGuildFromAllAlliances(Guild toRemove) { + + if (!this.guildAlliances.containsKey(toRemove.getObjectUUID())) { + return false; + } + + if (!DbManager.GuildQueries.REMOVE_FROM_ALLIANCE_LIST(this.getObjectUUID(), toRemove.getObjectUUID())) + return false; + + + this.guildAlliances.remove(toRemove.getObjectUUID()); + + this.removeGuildFromAlliance(toRemove); + this.removeGuildFromEnemy(toRemove); + this.removeGuildFromRecommended(toRemove); + + Guild.UpdateClientAlliances(this); + + + return true; + + } + + +} diff --git a/src/engine/objects/GuildAlliances.java b/src/engine/objects/GuildAlliances.java new file mode 100644 index 00000000..7f43f1bb --- /dev/null +++ b/src/engine/objects/GuildAlliances.java @@ -0,0 +1,102 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.AllianceType; +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class GuildAlliances { + + private int sourceGuild; + private int allianceGuild; + private boolean isRecommended; + private boolean isAlly; + private String recommender; + + /** + * ResultSet Constructor + */ + + public GuildAlliances(ResultSet rs) throws SQLException { + this.sourceGuild = rs.getInt("GuildID"); + this.allianceGuild = rs.getInt("OtherGuildID"); + this.isRecommended = rs.getBoolean("isRecommended"); + this.isAlly = rs.getBoolean("isAlliance"); + this.recommender = rs.getString("recommender"); + } + + public GuildAlliances(int sourceGuild, int allianceGuild, boolean isRecommended, boolean isAlly, + String recommender) { + super(); + this.sourceGuild = sourceGuild; + this.allianceGuild = allianceGuild; + this.isRecommended = isRecommended; + this.isAlly = isAlly; + this.recommender = recommender; + } + + public int getSourceGuild() { + return sourceGuild; + } + + public int getAllianceGuild() { + return allianceGuild; + } + + public boolean isRecommended() { + return isRecommended; + } + + public boolean isAlly() { + return isAlly; + } + + public String getRecommender() { + return recommender; + } + + public synchronized boolean UpdateAlliance(final AllianceType allianceType, boolean updateRecommended) { + switch (allianceType) { + case Ally: + if (updateRecommended) { + if (!DbManager.GuildQueries.UPDATE_ALLIANCE_AND_RECOMMENDED(this.sourceGuild, this.allianceGuild, true)) + return false; + this.isAlly = true; + this.isRecommended = false; + } else { + if (!DbManager.GuildQueries.UPDATE_ALLIANCE(this.sourceGuild, this.allianceGuild, true)) + return false; + this.isAlly = true; + this.isRecommended = false; + } + break; + case Enemy: + + if (updateRecommended) { + if (!DbManager.GuildQueries.UPDATE_ALLIANCE_AND_RECOMMENDED(this.sourceGuild, this.allianceGuild, false)) + return false; + this.isAlly = false; + this.isRecommended = false; + } else { + if (!DbManager.GuildQueries.UPDATE_ALLIANCE(this.sourceGuild, this.allianceGuild, false)) + return false; + this.isAlly = false; + this.isRecommended = false; + } + break; + + } + return true; + } + +} diff --git a/src/engine/objects/GuildCondemn.java b/src/engine/objects/GuildCondemn.java new file mode 100644 index 00000000..8aceafc2 --- /dev/null +++ b/src/engine/objects/GuildCondemn.java @@ -0,0 +1,68 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + + +public class GuildCondemn { + + public static HashMap> GetCondemnedFromGuildID = new HashMap<>(); + private int ID; + private int playerUID; + private int parentGuildUID; + private int guildUID; + private int friendType; + + + /** + * ResultSet Constructor + */ + public GuildCondemn(ResultSet rs) throws SQLException { + this.playerUID = rs.getInt("playerUID"); + this.parentGuildUID = rs.getInt("buildingUID"); + this.guildUID = rs.getInt("guildUID"); + this.friendType = rs.getInt("friendType"); + } + + + public GuildCondemn(int playerUID, int parentGuildUID, int guildUID, int friendType) { + super(); + this.playerUID = playerUID; + this.parentGuildUID = parentGuildUID; + this.guildUID = guildUID; + this.friendType = friendType; + } + + + public int getPlayerUID() { + return playerUID; + } + + + public int getParentGuildUID() { + return parentGuildUID; + } + + + public int getGuildUID() { + return guildUID; + } + + + public int getFriendType() { + return friendType; + } + + +} diff --git a/src/engine/objects/GuildHistory.java b/src/engine/objects/GuildHistory.java new file mode 100644 index 00000000..6c29820a --- /dev/null +++ b/src/engine/objects/GuildHistory.java @@ -0,0 +1,89 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + + +import engine.Enum.GameObjectType; +import engine.Enum.GuildHistoryType; +import engine.net.ByteBufferWriter; +import org.joda.time.DateTime; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class GuildHistory { + + + private int guildID; + private String guildName; + private DateTime time; + private GuildHistoryType historyType; + + + public GuildHistory(int guildID, String guildName, + DateTime dateTime, GuildHistoryType historyType) { + super(); + this.guildID = guildID; + this.guildName = guildName; + this.time = dateTime; + this.historyType = historyType; + + } + + public GuildHistory(ResultSet rs) throws SQLException { + java.util.Date sqlDateTime; + this.guildID = rs.getInt("guildID"); + Guild guild = Guild.getGuild(this.guildID); + if (guild != null) + this.guildName = guild.getName(); + else + this.guildName = "Guild Not Found"; + + sqlDateTime = rs.getTimestamp("historyDate"); + if (sqlDateTime != null) + this.time = new DateTime(sqlDateTime); + else + this.time = DateTime.now().minusYears(1); + this.historyType = GuildHistoryType.valueOf(rs.getString("historyType")); + } + + public GuildHistoryType getHistoryType() { + return historyType; + } + + public long getGuildID() { + return guildID; + } + + public String getGuildName() { + return guildName; + } + + + public void _serialize(ByteBufferWriter writer) { + writer.putInt(this.historyType.getType()); + writer.putInt(GameObjectType.Guild.ordinal()); + writer.putInt(this.guildID); + writer.putString(guildName); + writer.putInt(0); //Pad + writer.putDateTime(this.time); + } + + public DateTime getTime() { + return time; + } + + public void setTime(DateTime time) { + this.time = time; + } + + +} diff --git a/src/engine/objects/GuildStatusController.java b/src/engine/objects/GuildStatusController.java new file mode 100644 index 00000000..6c7f8631 --- /dev/null +++ b/src/engine/objects/GuildStatusController.java @@ -0,0 +1,130 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.util.concurrent.atomic.AtomicInteger; + +public class GuildStatusController { + + /* + * Status is stored in a single integer contained within the Character Table + * + * This class is responsible for maintaining and interpreting that value. + * + * Byte 1 - All : Title [0x000000FF] + * Byte 2 - Low : isFullMember [0x00000F00] + * Byte 2 - High : isTaxCollector [0x0000F000] + * Byte 3 - Low : isRecruiter [0x000F0000] + * Byte 3 - High : isInnerCouncil [0x00F00000] + * Byte 4 - Low : isGuildLeader [0x0F000000] + * Byte 4 - High : Empty [0xF0000000] + */ + + //Constants + private static final int TITLE = 0x000000FF; // 00, F0 and 0F had no effect + private static final int FULLMEMBER = 0x00000F00; + private static final int TAXCOLLECTOR = 0x0000F000; + private static final int RECRUITER = 0x000F0000; + private static final int INNERCOUNCIL = 0x00F00000; + private static final int GUILDLEADER = 0x0F000000; + + //Getters + public static boolean isGuildLeader(AtomicInteger status) { + return ((status.get() & GUILDLEADER) > 0); + } + + public static boolean isInnerCouncil(AtomicInteger status) { + return ((status.get() & INNERCOUNCIL) > 0); + } + + public static boolean isRecruiter(AtomicInteger status) { + return ((status.get() & RECRUITER) > 0); + } + + public static boolean isTaxCollector(AtomicInteger status) { + return ((status.get() & TAXCOLLECTOR) > 0); + } + + public static boolean isFullMember(AtomicInteger status) { + return ((status.get() & FULLMEMBER) > 0); + } + + public static int getTitle(AtomicInteger status) { + return status.get() & TITLE; + } + + public static int getRank(AtomicInteger status) { + int value = status.get(); + + //Guild Leader + if (value > 0x00FFFFFF) { + return 10; + } + + //Inner Council + if (value > 0x000FFFFF) { + return 9; + } + + //Recruiter + if (value > 0x0000FFFF) { + return 8; + } + + //Tax Collector + if (value > 0x00000FFF) { + return 7; + } + + //Full Member + if (value > 0x000000FF) { + return 6; + } + + //Petitioner + return 5; + } + + //Setters + public static void setTitle(AtomicInteger current, int i) { + int value; + i &= TITLE; + do { + value = current.get(); + } while (!current.compareAndSet(value, (value & ~TITLE) | i)); + } + + public static void setFullMember(AtomicInteger status, boolean newValue) { + setNibble(status, newValue, FULLMEMBER); + } + + public static void setTaxCollector(AtomicInteger status, boolean newValue) { + setNibble(status, newValue, TAXCOLLECTOR); + } + + public static void setRecruiter(AtomicInteger status, boolean newValue) { + setNibble(status, newValue, RECRUITER); + } + + public static void setInnerCouncil(AtomicInteger status, boolean newValue) { + setNibble(status, newValue, INNERCOUNCIL); + } + + public static void setGuildLeader(AtomicInteger status, boolean newValue) { + setNibble(status, newValue, GUILDLEADER); + } + + private static void setNibble(AtomicInteger current, boolean newValue, int mask) { + int value, i = ((newValue) ? mask & -1 : 0); + do { + value = current.get(); + } while (!current.compareAndSet(value, (value & ~mask) | i)); + } +} diff --git a/src/engine/objects/GuildTag.java b/src/engine/objects/GuildTag.java new file mode 100644 index 00000000..60a6d4b9 --- /dev/null +++ b/src/engine/objects/GuildTag.java @@ -0,0 +1,91 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; + +public class GuildTag { + public static final GuildTag ERRANT = new GuildTag(16, 16, 16, 0, 0); + public final int backgroundColor01; + public final int backgroundColor02; + public final int symbolColor; + public final int symbol; + public final int backgroundDesign; + + public GuildTag(int backgroundColor01, int backgroundColor02, + int symbolColor, int symbol, int backgroundDesign) { + super(); + this.backgroundColor01 = backgroundColor01; + this.backgroundColor02 = backgroundColor02; + this.symbolColor = symbolColor; + this.symbol = symbol; + this.backgroundDesign = backgroundDesign; + } + + public GuildTag(ByteBufferReader reader, boolean forCreation) { + this.backgroundColor01 = reader.getInt(); + this.backgroundColor02 = reader.getInt(); + this.symbolColor = reader.getInt(); + if (forCreation) { + this.symbol = reader.getInt(); + this.backgroundDesign = reader.getInt(); + } else { + this.backgroundDesign = reader.getInt(); + this.symbol = reader.getInt(); + } + } + + public GuildTag(ByteBufferReader reader) { + this(reader, false); + } + + public static void _serializeForGuildCreation(GuildTag guildTag, ByteBufferWriter writer) { + writer.putInt(guildTag.backgroundColor01); + writer.putInt(guildTag.backgroundColor02); + writer.putInt(guildTag.symbolColor); + writer.putInt(guildTag.symbol); + writer.putInt(guildTag.backgroundDesign); + } + + public static void _serializeForDisplay(GuildTag guildTag, ByteBufferWriter writer) { + writer.putInt(guildTag.backgroundColor01); + writer.putInt(guildTag.backgroundColor02); + writer.putInt(guildTag.symbolColor); + writer.putInt(guildTag.backgroundDesign); + writer.putInt(guildTag.symbol); + } + + public boolean isValid() { + if (this.backgroundColor01 < 0 || this.backgroundColor01 > 18) + return false; + if (this.backgroundColor02 < 0 || this.backgroundColor02 > 18) + return false; + if (this.symbolColor < 0 || this.symbolColor > 18) + return false; + if (this.symbol < 0 || this.symbol > 183) + return false; + return this.backgroundDesign >= 0 && this.backgroundDesign <= 14; + } + + public void serializeObject(ByteBufferWriter writer) { + writer.put((byte) this.backgroundColor01); + writer.put((byte) this.backgroundColor02); + writer.put((byte) this.symbolColor); + writer.put((byte) this.backgroundDesign); + writer.put((byte) this.symbol); + } + + + public String summarySentence() { + return "Bkgrnd: " + this.backgroundDesign + '(' + this.backgroundColor01 + '-' + this.backgroundColor02 + ')' + + "; Symbol: " + this.symbol + '(' + this.symbolColor + ')'; + } +} diff --git a/src/engine/objects/Heraldry.java b/src/engine/objects/Heraldry.java new file mode 100644 index 00000000..b60fda8d --- /dev/null +++ b/src/engine/objects/Heraldry.java @@ -0,0 +1,170 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class Heraldry { + + public static HashMap> HeraldyMap = new HashMap<>(); + public int playerUID; + public int characterUUID; + public int characterType; + + /** + * ResultSet Constructor + */ + + public Heraldry(ResultSet rs) throws SQLException { + this.playerUID = rs.getInt("playerUID"); + this.characterUUID = rs.getInt("characterUID"); + this.characterType = rs.getInt("characterType"); + + //cache player friends. + //hashset already created, just add to set. + if (HeraldyMap.containsKey(playerUID)) { + HashMap playerHeraldySet = HeraldyMap.get(playerUID); + playerHeraldySet.put(characterUUID, characterType); + //hashset not yet created, create new set, and add to map. + } else { + HashMap playerHeraldySet = new HashMap<>(); + playerHeraldySet.put(characterUUID, characterType); + HeraldyMap.put(this.playerUID, playerHeraldySet); + } + + } + + public Heraldry(int playerUID, int friendUID) { + super(); + this.playerUID = playerUID; + this.characterUUID = friendUID; + } + + public static boolean AddToHeraldy(int playerID, AbstractWorldObject character) { + HashMap characters = HeraldyMap.get(playerID); + + if (characters != null) { + //already in friends list, don't do anything. + if (characters.containsKey(character.getObjectUUID())) + return false; + + DbManager.PlayerCharacterQueries.ADD_HERALDY(playerID, character); + characters.put(character.getObjectUUID(), character.getObjectType().ordinal()); + } else { + characters = new HashMap<>(); + DbManager.PlayerCharacterQueries.ADD_HERALDY(playerID, character); + characters.put(character.getObjectUUID(), character.getObjectType().ordinal()); + HeraldyMap.put(playerID, characters); + } + return true; + } + + public static boolean RemoveFromHeraldy(int playerID, int characterID) { + + if (!CanRemove(playerID, characterID)) + return false; + + HashMap characters = HeraldyMap.get(playerID); + + if (characters != null) { + DbManager.PlayerCharacterQueries.REMOVE_HERALDY(playerID, characterID); + characters.remove(characterID); + } + return true; + } + + public static boolean CanRemove(int playerID, int toRemove) { + if (HeraldyMap.get(playerID) == null) + return false; + + if (HeraldyMap.get(playerID).isEmpty()) + return false; + + if (!HeraldyMap.get(playerID).containsKey(toRemove)) + return false; + + return true; + } + + public static void AuditHeraldry() { + + HashMap characterMap; + ArrayList purgeList = new ArrayList<>(); + + for (int playerID : Heraldry.HeraldyMap.keySet()) { + + characterMap = Heraldry.HeraldyMap.get(playerID); + + if (characterMap == null || characterMap.isEmpty()) + continue; + + // Loop through map adding deleted characters to our purge map + + purgeList.clear(); + + for (int characterID : characterMap.keySet()) { + + int characterType = characterMap.get(characterID); + + if (characterType != GameObjectType.PlayerCharacter.ordinal()) + continue; + + // Player is deleted, add to purge list + + if (PlayerCharacter.getFromCache(characterID) == null) + purgeList.add(characterID); + + } + + // Run purge + + for (int uuid : purgeList) { + + if (!Heraldry.RemoveFromHeraldy(playerID, uuid)) + continue; + + Logger.info("Removed Deleted Character ID " + uuid + " from PlayerID " + playerID + " heraldry."); + + } + } + } + + public static void ValidateHeraldry(int playerUUID) { + + HashMap heraldryMap = Heraldry.HeraldyMap.get(playerUUID); + + if (heraldryMap == null || heraldryMap.isEmpty()) + return; + + for (int characterID : heraldryMap.keySet()) { + int characterType = heraldryMap.get(characterID); + + GameObjectType objectType = GameObjectType.values()[characterType]; + + AbstractGameObject ago = DbManager.getFromCache(objectType, characterID); + + if (ago == null) + heraldryMap.remove(characterID); + + } + } + + public int getPlayerUID() { + return playerUID; + } + +} diff --git a/src/engine/objects/Item.java b/src/engine/objects/Item.java new file mode 100644 index 00000000..cc74c165 --- /dev/null +++ b/src/engine/objects/Item.java @@ -0,0 +1,1455 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.*; +import engine.exception.SerializationException; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.net.ByteBufferReader; +import engine.net.ByteBufferWriter; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.DeleteItemMsg; +import engine.powers.EffectsBase; +import engine.powers.effectmodifiers.AbstractEffectModifier; +import engine.powers.poweractions.AbstractPowerAction; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + + +public class Item extends AbstractWorldObject { + + private static ConcurrentHashMap enchantValues = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final short durabilityMax; + private final byte chargesMax; + private final ConcurrentHashMap bonuses = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final ArrayList effectNames = new ArrayList<>(); + public Enum.ItemContainerType containerType; + public ReentrantLock lootLock = new ReentrantLock(); + private int ownerID; //may be character, account, npc, mob + private int flags; //1 = isIDed + private int numberOfItems; + private short durabilityCurrent; + private byte chargesRemaining; + private byte equipSlot; + private boolean canDestroy; + private boolean rentable; + private boolean isRandom = false; + private int value; + private OwnerType ownerType; + private int itemBaseID; + private AbstractWorldObject lastOwner; + private ArrayList enchants = new ArrayList<>(); + private long dateToUpgrade; + private String customName = ""; + private int magicValue; + + /** + * No Id Constructor + */ + public Item(ItemBase itemBase, int ownerID, + OwnerType ownerType, byte chargesMax, byte chargesRemaining, + short durabilityCurrent, short durabilityMax, boolean canDestroy, + boolean rentable, Enum.ItemContainerType containerType, byte equipSlot, + ArrayList enchants, String name) { + super(); + this.itemBaseID = itemBase.getUUID(); + this.ownerID = ownerID; + this.ownerType = ownerType; + + if (itemBase.getType().getValue() == 20) { + this.chargesMax = chargesMax; + this.chargesRemaining = chargesRemaining; + } else { + this.chargesMax = (byte) itemBase.getNumCharges(); + this.chargesRemaining = (byte) itemBase.getNumCharges(); + } + + this.durabilityMax = (short) itemBase.getDurability(); + this.durabilityCurrent = (short) itemBase.getDurability(); + this.containerType = containerType; + this.canDestroy = canDestroy; + this.rentable = rentable; + this.equipSlot = equipSlot; + this.enchants = enchants; + this.flags = 1; + this.value = this.magicValue; + this.customName = name; + + loadEnchantments(); + bakeInStats(); + } + + public Item(ItemBase itemBase, int ownerID, + OwnerType ownerType, byte chargesMax, byte chargesRemaining, + short durabilityCurrent, short durabilityMax, boolean canDestroy, + boolean rentable, boolean inBank, boolean inVault, + boolean inInventory, boolean isEquipped, boolean isForge, byte equipSlot, + ArrayList enchants) { + + super(); + this.itemBaseID = itemBase.getUUID(); + this.ownerID = ownerID; + this.ownerType = ownerType; + + this.chargesMax = (byte) itemBase.getNumCharges(); + this.chargesRemaining = (byte) itemBase.getNumCharges(); + + this.durabilityMax = (short) itemBase.getDurability(); + this.durabilityCurrent = (short) itemBase.getDurability(); + + this.canDestroy = canDestroy; + this.rentable = rentable; + + this.equipSlot = equipSlot; + this.enchants = enchants; + this.flags = 1; + + this.value = this.magicValue; + + loadEnchantments(); + bakeInStats(); + } + + /** + * Normal Constructor + */ + public Item(ItemBase itemBase, int ownerID, + OwnerType ownerType, byte chargesMax, byte chargesRemaining, + short durabilityCurrent, short durabilityMax, boolean canDestroy, + boolean rentable, boolean inBank, boolean inVault, + boolean inInventory, boolean isEquipped, byte equipSlot, + ArrayList enchants, int newUUID) { + + super(newUUID); + this.itemBaseID = itemBase.getUUID(); + this.ownerID = ownerID; + this.ownerType = ownerType; + this.customName = ""; + + this.chargesMax = (byte) itemBase.getNumCharges(); + this.chargesRemaining = (byte) itemBase.getNumCharges(); + + this.durabilityMax = (short) itemBase.getDurability(); + this.durabilityCurrent = (short) itemBase.getDurability(); + this.canDestroy = canDestroy; + this.rentable = rentable; + this.equipSlot = equipSlot; + this.enchants = enchants; + this.flags = 1; + this.value = this.magicValue; + + loadEnchantments(); + bakeInStats(); + } + + /** + * ResultSet Constructor + */ + public Item(ResultSet rs) throws SQLException { + super(rs); + + this.itemBaseID = rs.getInt("item_itemBaseID"); + + // Set container enumeration + + String container = rs.getString("item_container"); + + switch (container) { + case "inventory": + this.containerType = Enum.ItemContainerType.INVENTORY; + break; + case "bank": + this.containerType = Enum.ItemContainerType.BANK; + break; + case "vault": + this.containerType = Enum.ItemContainerType.VAULT; + break; + case "equip": + this.containerType = Enum.ItemContainerType.EQUIPPED; + break; + case "forge": + this.containerType = Enum.ItemContainerType.FORGE; + break; + case "warehouse": + this.containerType = Enum.ItemContainerType.FORGE; + break; + } + + this.ownerID = rs.getInt("parent"); + + if (this.getItemBase() != null) + this.chargesMax = (byte) this.getItemBase().getNumCharges(); + else + this.chargesMax = 0; + + this.chargesRemaining = rs.getByte("item_chargesRemaining"); + + this.durabilityCurrent = rs.getShort("item_durabilityCurrent"); + this.durabilityMax = rs.getShort("item_durabilityMax"); + + DbObjectType ownerType; + ownerType = DbManager.BuildingQueries.GET_UID_ENUM(this.ownerID); + + switch (ownerType) { + case CHARACTER: + this.ownerType = OwnerType.PlayerCharacter; + break; + case NPC: + this.ownerType = OwnerType.Npc; + break; + case ACCOUNT: + this.ownerType = OwnerType.Account; + break; + } + + this.canDestroy = true; + + this.equipSlot = rs.getByte("item_equipSlot"); + + this.numberOfItems = rs.getInt("item_numberOfItems"); + + this.flags = rs.getInt("item_flags"); + this.dateToUpgrade = rs.getLong("item_dateToUpgrade"); + this.value = rs.getInt("item_value"); + this.customName = rs.getString("item_name"); + + + } + + public static void _serializeForClientMsg(Item item, ByteBufferWriter writer) + throws SerializationException { + Item._serializeForClientMsg(item, writer, true); + } + + public static void serializeForClientMsgWithoutSlot(Item item, ByteBufferWriter writer) { + Item._serializeForClientMsg(item, writer, false); + } + + public static void serializeForClientMsgForVendor(Item item, ByteBufferWriter writer, float percent) { + Item._serializeForClientMsg(item, writer, true); + int baseValue = item.magicValue; + writer.putInt(baseValue); + writer.putInt((int) (baseValue * percent)); + } + + public static void serializeForClientMsgForVendorWithoutSlot(Item item, ByteBufferWriter writer, float percent) { + Item._serializeForClientMsg(item, writer, false); + writer.putInt(item.getValue()); + writer.putInt(item.getValue()); + } + + public static void _serializeForClientMsg(Item item, ByteBufferWriter writer, + boolean includeSlot) { + if (includeSlot) + writer.putInt(item.equipSlot); + writer.putInt(0); // Pad + writer.putInt(item.getItemBase().getUUID()); + + writer.putInt(item.getObjectType().ordinal()); + writer.putInt(item.getObjectUUID()); + + // Unknown statics + for (int i = 0; i < 3; i++) { + writer.putInt(0); // Pad + } + for (int i = 0; i < 4; i++) { + writer.putInt(0x3F800000); // Static + } + for (int i = 0; i < 5; i++) { + writer.putInt(0); // Pad + } + for (int i = 0; i < 2; i++) { + writer.putInt(0xFFFFFFFF); // Static + } + + // Handle Hair / Beard / horns Color. + boolean isHair = (item.equipSlot == (byte) MBServerStatics.SLOT_HAIRSTYLE); + boolean isBeard = (item.equipSlot == (byte) MBServerStatics.SLOT_BEARDSTYLE); + int itemColor = 0; + if (isHair || isBeard) { + PlayerCharacter pc = PlayerCharacter.getFromCache(item.ownerID); + if (pc != null) + if (isHair) + itemColor = pc.getHairColor(); + else if (isBeard) + itemColor = pc.getBeardColor(); + } + writer.putInt(itemColor); + + writer.put((byte) 1); // End Datablock byte + if (item.customName.isEmpty() || item.customName.isEmpty()) { + writer.putInt(0); + } else + writer.putString(item.customName); // Unknown. pad? + writer.put((byte) 1); // End Datablock byte + + writer.putFloat((float) item.durabilityMax); + writer.putFloat((float) item.durabilityCurrent); + + writer.put((byte) 1); // End Datablock byte + + writer.putInt(0); // Pad + writer.putInt(0); // Pad + + if (item.getItemBase().equals(ItemBase.GOLD_ITEM_BASE)) { + + if (item.getOwner() != null && item.getOwner().getObjectType() == GameObjectType.PlayerCharacter) { + PlayerCharacter player = (PlayerCharacter) item.getOwner(); + int tradingAmount = player.getCharItemManager().getGoldTrading(); + writer.putInt(item.numberOfItems - tradingAmount); + } else + writer.putInt(item.numberOfItems); // Amount of gold + } else + writer.putInt(item.getItemBase().getBaseValue()); + + writer.putInt(item.getValue()); + + int effectsSize = item.effects.size(); + ArrayList effs = null; + Effect nextE = null; + if (effectsSize > 0 && item.isID()) { + effs = new ArrayList<>(item.effects.values()); + + //Don't send effects that have a token of 1 + Iterator efi = effs.iterator(); + while (efi.hasNext()) { + nextE = efi.next(); + if (nextE.getEffectToken() == 1 || nextE.bakedInStat()) + efi.remove(); + } + } else + effs = new ArrayList<>(); + + int effectsToSendSize = effs.size(); + writer.putInt(effectsToSendSize); + for (int i = 0; i < effectsToSendSize; i++) { + effs.get(i).serializeForItem(writer, item); + } + writer.putInt(0x00000000); + + + if (effectsSize > 0) + if (item.isID()) + writer.putInt(36); //Magical, blue name + else + writer.putInt(40); //Magical, unidentified + else if (item.getItemBase().getBakedInStats().size() > 0) + writer.putInt(36); //Magical, blue name + else + writer.putInt(4); //Non-Magical, grey name + writer.putInt(item.chargesRemaining); + writer.putInt(0); // Pad + writer.putInt(item.numberOfItems); + writer.put((byte) 0); + + + if (item.getItemBase().getType().getValue() != 20) { + writer.putShort((short) 0); + return; + } + writer.put((byte) 1); // + writer.putInt(0); + writer.putInt(0); + if (item.chargesRemaining == 0) + writer.putInt(1); + else + writer.putInt(item.chargesRemaining); + writer.put((byte) 0); + } + + public static void SerializeTradingGold(PlayerCharacter player, ByteBufferWriter writer) { + + writer.putInt(0); // Pad + writer.putInt(7); + + writer.putInt(GameObjectType.Item.ordinal()); + writer.putInt(player.getObjectUUID()); + + // Unknown statics + for (int i = 0; i < 3; i++) { + writer.putInt(0); // Pad + } + for (int i = 0; i < 4; i++) { + writer.putInt(0x3F800000); // Static + } + for (int i = 0; i < 5; i++) { + writer.putInt(0); // Pad + } + for (int i = 0; i < 2; i++) { + writer.putInt(0xFFFFFFFF); // Static + } + + // Handle Hair / Beard / horns Color. + + int itemColor = 0; + writer.putInt(itemColor); + + writer.put((byte) 1); // End Datablock byte + writer.putInt(0); + writer.put((byte) 1); // End Datablock byte + + writer.putFloat((float) 1); + writer.putFloat((float) 1); + + writer.put((byte) 1); // End Datablock byte + + writer.putInt(0); // Pad + writer.putInt(0); // Pad + + + writer.putInt(player.getCharItemManager().getGoldTrading()); // Amount of gold + + + writer.putInt(0); + + + writer.putInt(0); + + writer.putInt(0x00000000); + + writer.putInt(4); //Non-Magical, grey name + writer.putInt(1); + writer.putInt(0); // Pad + writer.putInt(player.getCharItemManager().getGoldTrading()); + writer.put((byte) 0); + + writer.putShort((short) 0); + + } + + public static boolean MakeItemForPlayer(ItemBase toCreate, PlayerCharacter reciever, int amount) { + + boolean itemWorked = false; + + Item item = new Item(toCreate, reciever.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0, + (short) 1, (short) 1, true, false, Enum.ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + + synchronized (item) { + item.numberOfItems = amount; + } + item.containerType = Enum.ItemContainerType.INVENTORY; + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + itemWorked = true; + } catch (Exception e) { + Logger.error(e); + } + + if (!itemWorked) + return false; + + reciever.getCharItemManager().addItemToInventory(item); + reciever.getCharItemManager().updateInventory(); + + return true; + } + + public static Item deserializeFromClientMsg(ByteBufferReader reader, + boolean includeSlot) { + if (includeSlot) + reader.getInt(); + reader.getInt(); + int itemBase = reader.getInt(); //itemBase + int objectType = reader.getInt(); //object type; + int UUID = reader.getInt(); + for (int i = 0; i < 14; i++) { + reader.getInt(); // Pads and statics + } + int unknown = reader.getInt(); + + byte readString = reader.get(); + if (readString == 1) + reader.getString(); + byte readDurability = reader.get(); + if (readDurability == 1) { + reader.getInt(); + reader.getInt(); + } + + byte readEnchants = reader.get(); + if (readEnchants == 1) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + reader.getInt(); + int enchantSize = reader.getInt(); + for (int i = 0; i < enchantSize; i++) { + reader.getInt(); //effect token + reader.getInt(); //trains + int type = reader.getInt(); + reader.get(); + if (type == 1) + reader.getLong(); //item comp + else + reader.getInt(); //power token + reader.getString(); //name + reader.getFloat(); //duration + } + for (int i = 0; i < 5; i++) { + reader.getInt(); + } + } + + reader.get(); + byte isContract = reader.get(); + if (isContract == 1) { + reader.getInt(); + reader.getInt(); + reader.getInt(); + } + reader.get(); + + if (UUID == 0 || objectType == 0) + return null; + if (objectType == GameObjectType.MobLoot.ordinal()) + return MobLoot.getFromCache(UUID); + return Item.getFromCache(UUID); + } + + public static void putListForVendor(ByteBufferWriter writer, ArrayList list, NPC vendor) { + putList(writer, list, false, vendor.getObjectUUID(), true, vendor); + } + + public static void putList(ByteBufferWriter writer, ArrayList list, boolean includeSlot, int ownerID) { + putList(writer, list, includeSlot, ownerID, false, null); + } + + private static void putList(ByteBufferWriter writer, ArrayList list, boolean includeSlot, int ownerID, boolean forVendor, NPC vendor) { + int indexPosition = writer.position(); + //reserve 4 bytes for index. + writer.putInt(0); + + int serialized = 0; + for (Item item : list) { + + if (item.getItemBase().getType().equals(ItemType.GOLD)) + if (item.numberOfItems == 0) + continue; + try { + if (includeSlot && !forVendor) + Item._serializeForClientMsg(item, writer); + else if (!includeSlot && !forVendor) + Item.serializeForClientMsgWithoutSlot(item, writer); + + if (!includeSlot && forVendor) //TODO separate for sell/buy percent + + Item.serializeForClientMsgForVendorWithoutSlot(item, writer, vendor.getSellPercent()); + + if (includeSlot && forVendor) //TODO separate for sell/buy percent + + Item.serializeForClientMsgForVendor(item, writer, vendor.getSellPercent()); + + } catch (SerializationException se) { + continue; + } + ++serialized; + } + + writer.putIntAt(serialized, indexPosition); + } + + public static void putTradingList(PlayerCharacter player, ByteBufferWriter writer, ArrayList list, boolean includeSlot, int ownerID, boolean forVendor, NPC vendor) { + int indexPosition = writer.position(); + //reserve 4 bytes for index. + writer.putInt(0); + + int serialized = 0; + for (Item item : list) { + + if (item.getItemBase().getType().equals(ItemType.GOLD)) + if (item.numberOfItems == 0) + continue; + try { + if (includeSlot && !forVendor) + Item._serializeForClientMsg(item, writer); + else if (!includeSlot && !forVendor) + Item.serializeForClientMsgWithoutSlot(item, writer); + + if (!includeSlot && forVendor) //TODO separate for sell/buy percent + + Item.serializeForClientMsgForVendorWithoutSlot(item, writer, vendor.getSellPercent()); + + if (includeSlot && forVendor) //TODO separate for sell/buy percent + + Item.serializeForClientMsgForVendor(item, writer, vendor.getSellPercent()); + + } catch (SerializationException se) { + continue; + } + ++serialized; + } + if (player.getCharItemManager().getGoldTrading() > 0) { + Item.SerializeTradingGold(player, writer); + ++serialized; + } + + + writer.putIntAt(serialized, indexPosition); + } + + public static Item createItemForPlayer(PlayerCharacter pc, ItemBase ib) { + Item item = null; + byte charges = 0; + + charges = (byte) ib.getNumCharges(); + + short durability = (short) ib.getDurability(); + + Item temp = new Item(ib, pc.getObjectUUID(), + OwnerType.PlayerCharacter, charges, charges, durability, durability, + true, false, Enum.ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + try { + item = DbManager.ItemQueries.ADD_ITEM(temp); + } catch (Exception e) { + Logger.error(e); + } + return item; + } + + public static Item createItemForPlayerBank(PlayerCharacter pc, ItemBase ib) { + Item item = null; + byte charges = 0; + + charges = (byte) ib.getNumCharges(); + + short durability = (short) ib.getDurability(); + + Item temp = new Item(ib, pc.getObjectUUID(), + OwnerType.PlayerCharacter, charges, charges, durability, durability, + true, false, Enum.ItemContainerType.BANK, (byte) 0, + new ArrayList<>(), ""); + try { + item = DbManager.ItemQueries.ADD_ITEM(temp); + } catch (Exception e) { + } + return item; + } + + public static Item createItemForMob(Mob mob, ItemBase ib) { + Item item = null; + byte charges = 0; + + charges = (byte) ib.getNumCharges(); + short durability = (short) ib.getDurability(); + + Item temp = new Item(ib, mob.getObjectUUID(), + OwnerType.Mob, charges, charges, durability, durability, + true, false, Enum.ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + try { + item = DbManager.ItemQueries.ADD_ITEM(temp); + } catch (Exception e) { + Logger.error(e); + } + return item; + } + + public static Item getFromCache(int id) { + return (Item) DbManager.getFromCache(GameObjectType.Item, id); + } + + public static Item newGoldItem(AbstractWorldObject awo, ItemBase ib, Enum.ItemContainerType containerType) { + return newGoldItem(awo, ib, containerType, true); + } + + //used for vault! + public static Item newGoldItem(int accountID, ItemBase ib, Enum.ItemContainerType containerType) { + return newGoldItem(accountID, ib, containerType, true); + } + + private static Item newGoldItem(int accountID, ItemBase ib, Enum.ItemContainerType containerType, boolean persist) { + + int ownerID; + OwnerType ownerType; + + ownerID = accountID; + ownerType = OwnerType.Account; + + + Item newGold = new Item(ib, ownerID, ownerType, + (byte) 0, (byte) 0, (short) 0, (short) 0, true, false, containerType, (byte) 0, + new ArrayList<>(), ""); + + synchronized (newGold) { + newGold.numberOfItems = 0; + } + + if (persist) { + try { + newGold = DbManager.ItemQueries.ADD_ITEM(newGold); + if (newGold != null) { + synchronized (newGold) { + newGold.numberOfItems = 0; + } + } + } catch (Exception e) { + Logger.error(e); + } + DbManager.ItemQueries.ZERO_ITEM_STACK(newGold); + } + + return newGold; + } + + private static Item newGoldItem(AbstractWorldObject awo, ItemBase ib, Enum.ItemContainerType containerType, boolean persist) { + + int ownerID; + OwnerType ownerType; + + if (awo.getObjectType().equals(GameObjectType.Mob)) + return null; + + if (containerType == Enum.ItemContainerType.VAULT) { + if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) { + Logger.error("AWO is not a PlayerCharacter"); + return null; + } + ownerID = ((PlayerCharacter) awo).getAccount().getObjectUUID(); + ownerType = OwnerType.Account; + } else { + + ownerID = awo.getObjectUUID(); + + switch (awo.getObjectType()) { + + case NPC: + ownerType = OwnerType.Npc; + break; + case PlayerCharacter: + ownerType = OwnerType.PlayerCharacter; + break; + case Mob: + ownerType = OwnerType.Mob; + break; + default: + Logger.error("Unsupported AWO object type."); + return null; + } + } + + Item newGold = new Item(ib, ownerID, ownerType, + (byte) 0, (byte) 0, (short) 0, (short) 0, true, false, containerType, (byte) 0, + new ArrayList<>(), ""); + + synchronized (newGold) { + newGold.numberOfItems = 0; + } + + if (persist) { + try { + newGold = DbManager.ItemQueries.ADD_ITEM(newGold); + if (newGold != null) { + synchronized (newGold) { + newGold.numberOfItems = 0; + } + } + } catch (Exception e) { + Logger.error(e); + } + DbManager.ItemQueries.ZERO_ITEM_STACK(newGold); + } + newGold.containerType = containerType; + + return newGold; + } + + // This is to be used for trades - the new item is not stored in the database + public static Item newGoldItemTemp(AbstractWorldObject awo, ItemBase ib) { + return Item.newGoldItem(awo, ib, Enum.ItemContainerType.NONE, false); + } + + public static Item getItem(int UUID) { + if (UUID == 0) + return null; + + Item item = (Item) DbManager.getFromCache(GameObjectType.Item, UUID); + if (item != null) + return item; + return DbManager.ItemQueries.GET_ITEM(UUID); + } + + public static void addEnchantValue(String enchant, int value) { + Item.enchantValues.put(enchant, value); + } + + public static int getEnchantValue(String enchant) { + if (Item.enchantValues.containsKey(enchant)) + return Item.enchantValues.get(enchant); + return 0; + } + + public String getCustomName() { + return customName; + } + + public ItemBase getItemBase() { + return ItemBase.getItemBase(itemBaseID); + } + + public int getItemBaseID() { + return this.itemBaseID; + } + + public int getOwnerID() { + return ownerID; + } + + //Only to be used for trading + public void setOwnerID(int ownerID) { + this.ownerID = ownerID; + } + + public OwnerType getOwnerType() { + return ownerType; + } + + public AbstractGameObject getOwner() { + if (this.ownerType == OwnerType.Npc) + return NPC.getFromCache(this.ownerID); + else if (this.ownerType == OwnerType.PlayerCharacter) + return PlayerCharacter.getFromCache(this.ownerID); + else if (this.ownerType == OwnerType.Mob) + return Mob.getFromCache(this.ownerID); + else if (this.ownerType == OwnerType.Account) + return DbManager.AccountQueries.GET_ACCOUNT(this.ownerID); + else + return null; + } + + public boolean setOwner(AbstractGameObject owner) { + if (owner == null) + return false; + if (owner.getObjectType().equals(GameObjectType.NPC)) + this.ownerType = OwnerType.Npc; + else if (owner.getObjectType().equals(GameObjectType.PlayerCharacter)) + this.ownerType = OwnerType.PlayerCharacter; + else if (owner.getObjectType().equals(GameObjectType.Mob)) + this.ownerType = OwnerType.Mob; + else if (owner.getObjectType().equals(GameObjectType.Account)) + this.ownerType = OwnerType.Account; + else + return false; + this.ownerID = owner.getObjectUUID(); + return true; + } + + public boolean isOwnerNPC() { + return (ownerType == OwnerType.Npc); + } + + public boolean isOwnerCharacter() { + return (ownerType == OwnerType.PlayerCharacter); + } + + public boolean isOwnerAccount() { + return (ownerType == OwnerType.Account); + } + + public byte getChargesMax() { + return chargesMax; + } + + public byte getChargesRemaining() { + return chargesRemaining; + } + + public short getDurabilityCurrent() { + return durabilityCurrent; + } + + public void setDurabilityCurrent(short value) { + this.durabilityCurrent = value; + } + + public short getDurabilityMax() { + return durabilityMax; + } + + public boolean isCanDestroy() { + return canDestroy; + } + + public boolean isRentable() { + return rentable; + } + + public byte getEquipSlot() { + return equipSlot; + } + + public ArrayList getEnchants() { + return enchants; + } + + public int getNumOfItems() { + return this.numberOfItems; + } + + public synchronized void setNumOfItems(int numberOfItems) { + this.numberOfItems = numberOfItems; + } + + public ConcurrentHashMap getBonuses() { + return this.bonuses; + } + + public void clearBonuses() { + this.bonuses.clear(); + } + + public float getBonus(ModType modType, SourceType sourceType) { + + int amount = 0; + for (AbstractEffectModifier modifier : this.getBonuses().keySet()) { + if (modifier.getPercentMod() != 0) + continue; + if (modifier.modType.equals(modType) == false || modifier.sourceType.equals(sourceType) == false) + continue; + amount += this.bonuses.get(modifier); + } + return amount; + } + + public float getBonusPercent(ModType modType, SourceType sourceType) { + + int amount = 0; + for (AbstractEffectModifier modifier : this.getBonuses().keySet()) { + + if (modifier.getPercentMod() == 0) + continue; + if (modifier.modType.equals(modType) == false || modifier.sourceType.equals(sourceType) == false) + continue; + amount += this.bonuses.get(modifier); + } + return amount; + } + + public boolean isID() { + return ((this.flags & 1) > 0); + } + + public void setIsID(boolean value) { + if (value) + this.flags |= 1; + else + this.flags &= ~1; + } + + public void setIsComplete(boolean value) { + if (value) + this.flags |= 2; + else + this.flags &= ~2; + } + + public boolean isComplete() { + return this.dateToUpgrade < System.currentTimeMillis() + 1000; + } + + public String getContainerInfo() { + String ret = "OwnerID: " + this.ownerID + ", container: "; + ret += this.containerType.toString(); + ret += "Equip Slot: " + this.equipSlot; + return ret; + } + + public int getFlags() { + return this.flags; + } + + public void setFlags(int value) { + this.flags = value; + } + + public void addBonus(AbstractEffectModifier key, float amount) { + if (this.bonuses.containsKey(key)) + this.bonuses.put(key, (this.bonuses.get(key) + amount)); + else + this.bonuses.put(key, amount); + } + + public void multBonus(AbstractEffectModifier key, float amount) { + if (this.bonuses.containsKey(key)) + this.bonuses.put(key, (this.bonuses.get(key) * amount)); + else + this.bonuses.put(key, amount); + } + + public synchronized void decrementChargesRemaining() { + this.chargesRemaining -= 1; + if (this.chargesRemaining < 0) + this.chargesRemaining = 0; + DbManager.ItemQueries.UPDATE_REMAINING_CHARGES(this); + } + + protected void validateItemContainer() { + + if (this.containerType == Enum.ItemContainerType.NONE) + + if (this.ownerID != 0) + // Item has an owner, just somehow the flags got messed up. + // Default to bank. + // TODO NEED LOG EVENT HERE. + this.containerType = Enum.ItemContainerType.BANK; + else + // This item is on the ground. Nothing to worry about. + this.zeroItem(); + } + + // Removes all ownership of item and 'orphans' it. + protected synchronized void junk() { + + DbManager.ItemQueries.UPDATE_OWNER(this, 0, false, false, false, ItemContainerType.NONE, 0); + this.zeroItem(); + + //TODO do we want to delete the item here? + this.lastOwner = null; + //cleanup item from server. + this.removeFromCache(); + } + + public synchronized void zeroItem() { + this.ownerID = 0; + + this.ownerType = null; + this.containerType = Enum.ItemContainerType.NONE; + this.equipSlot = MBServerStatics.SLOT_UNEQUIPPED; + } + + protected synchronized boolean moveItemToInventory(PlayerCharacter pc) { + if (!DbManager.ItemQueries.UPDATE_OWNER(this, + pc.getObjectUUID(), //tableID + false, //isNPC + true, //isPlayer + false, //isAccount + ItemContainerType.INVENTORY, + 0)) //Slot + + return false; + + this.zeroItem(); + this.ownerID = pc.getObjectUUID(); + this.ownerType = OwnerType.PlayerCharacter; + this.containerType = ItemContainerType.INVENTORY; + return true; + } + + protected synchronized boolean moveItemToInventory(NPC npc) { + if (npc.isStatic()) { + if (!DbManager.ItemQueries.UPDATE_OWNER(this, 0, false, false, false, ItemContainerType.INVENTORY, 0)) + return false; + } else if (!DbManager.ItemQueries.UPDATE_OWNER(this, + npc.getObjectUUID(), //UUID + true, //isNPC + false, //isPlayer + false, //isAccount + ItemContainerType.INVENTORY, + 0)) //Slot + + return false; + this.zeroItem(); + this.ownerID = npc.getObjectUUID(); + this.ownerType = OwnerType.Npc; + this.containerType = Enum.ItemContainerType.INVENTORY; + return true; + } + + protected synchronized boolean moveItemToInventory(Corpse corpse) { + if (!DbManager.ItemQueries.UPDATE_OWNER(this, + 0, //no ID for corpse + false, //isNPC + true, //isPlayer + false, //isAccount + ItemContainerType.INVENTORY, + 0)) //Slot + + return false; + this.zeroItem(); + this.ownerID = 0; + this.ownerType = null; + this.containerType = Enum.ItemContainerType.INVENTORY; + return true; + } + + protected synchronized boolean moveItemToBank(PlayerCharacter pc) { + if (!DbManager.ItemQueries.UPDATE_OWNER(this, + pc.getObjectUUID(), //UUID + false, //isNPC + true, //isPlayer + false, //isAccount + ItemContainerType.BANK, + 0)) //Slot + + return false; + this.zeroItem(); + this.ownerID = pc.getObjectUUID(); + this.ownerType = OwnerType.PlayerCharacter; + this.containerType = Enum.ItemContainerType.BANK; + return true; + } + + protected synchronized boolean moveItemToBank(NPC npc) { + if (!DbManager.ItemQueries.UPDATE_OWNER(this, + npc.getObjectUUID(), //UUID + true, //isNPC + false, //isPlayer + false, //isAccount + ItemContainerType.BANK, + 0)) //Slot + + return false; + this.zeroItem(); + this.ownerID = npc.getObjectUUID(); + this.ownerType = OwnerType.Npc; + this.containerType = Enum.ItemContainerType.BANK; + return true; + } + + protected synchronized boolean moveItemToVault(Account a) { + if (!DbManager.ItemQueries.UPDATE_OWNER(this, + a.getObjectUUID(), //UUID + false, //isNPC + false, //isPlayer + true, //isAccount + ItemContainerType.VAULT, + 0)) //Slot + + return false; + this.zeroItem(); + this.ownerID = a.getObjectUUID(); + this.ownerType = OwnerType.Account; + this.containerType = Enum.ItemContainerType.VAULT; + return true; + } + + protected synchronized boolean equipItem(PlayerCharacter pc, byte slot) { + + if (!DbManager.ItemQueries.UPDATE_OWNER(this, + pc.getObjectUUID(), //tableID + false, //isNPC + true, //isPlayer + false, //isAccount + ItemContainerType.EQUIPPED, + slot)) //Slot + + return false; + this.zeroItem(); + this.ownerID = pc.getObjectUUID(); + this.ownerType = OwnerType.PlayerCharacter; + this.containerType = Enum.ItemContainerType.EQUIPPED; + this.equipSlot = slot; + return true; + } + + protected synchronized boolean equipItem(NPC npc, byte slot) { + if (!DbManager.ItemQueries.UPDATE_OWNER(this, + npc.getObjectUUID(), //UUID + true, //isNPC + false, //isPlayer + false, //isAccount + ItemContainerType.EQUIPPED, + slot)) //Slot + + return false; + this.zeroItem(); + this.ownerID = npc.getObjectUUID(); + this.ownerType = OwnerType.Npc; + this.containerType = Enum.ItemContainerType.EQUIPPED; + this.equipSlot = slot; + return true; + } + + protected synchronized boolean equipItem(Mob npc, byte slot) { + + this.zeroItem(); + this.ownerID = npc.getObjectUUID(); + this.ownerType = OwnerType.Mob; + this.containerType = Enum.ItemContainerType.EQUIPPED; + this.equipSlot = slot; + return true; + } + + public final int getMagicValue() { + return this.magicValue; + } + + public int getBaseValue() { + if (this.getItemBase() != null) + return this.getItemBase().getBaseValue(); + return 0; + } + + public AbstractWorldObject getLastOwner() { + return this.lastOwner; + } + + public void setLastOwner(AbstractWorldObject value) { + this.lastOwner = value; + } + + @Override + public String getName() { + if (this.customName.isEmpty()) + if (this.getItemBase() != null) + return this.getItemBase().getName(); + return this.customName; + } + + public void setName(String name) { + this.customName = name; + } + + private void bakeInStats() { + + EffectsBase effect; + + if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) + return; + + if (this.getItemBase() != null) + + for (Integer token : this.getItemBase().getBakedInStats().keySet()) { + + effect = PowersManager.getEffectByToken(token); + + if (effect == null) { + Logger.error("missing effect of token " + token); + continue; + } + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(effect.getIDString()); + apa.applyBakedInStatsForItem(this, this.getItemBase().getBakedInStats().get(token)); + } + } + + public final void loadEnchantments() { + //dont load mobloot enchantments, they arent in db. + if (this.getObjectType().equals(GameObjectType.MobLoot)) { + this.magicValue = this.getItemBase().getBaseValue() + calcMagicValue(); + return; + } + + ConcurrentHashMap enchantList = DbManager.EnchantmentQueries.GET_ENCHANTMENTS_FOR_ITEM(this.getObjectUUID()); + + for (String enchant : enchantList.keySet()) { + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchant); + if (apa != null) { + apa.applyEffectForItem(this, enchantList.get(enchant)); + this.effectNames.add(enchant); + } + } + + this.magicValue = this.getItemBase().getBaseValue() + calcMagicValue(); + } + + public HashMap getBakedInStats() { + if (this.getItemBase() != null) + return this.getItemBase().getBakedInStats(); + return null; + } + + public void clearEnchantments() { + + //Clear permanent enchantment out of database + DbManager.EnchantmentQueries.CLEAR_ENCHANTMENTS((long) this.getObjectUUID()); + + for (String name : this.getEffects().keySet()) { + Effect eff = this.getEffects().get(name); + if (!eff.bakedInStat()) + this.endEffect(name); + } + this.effectNames.clear(); + } + + public void addPermanentEnchantment(String enchantID, int rank) { + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID); + if (apa == null) + return; + + DbManager.EnchantmentQueries.CREATE_ENCHANTMENT_FOR_ITEM((long) this.getObjectUUID(), enchantID, rank); + apa.applyEffectForItem(this, rank); + this.effectNames.add(enchantID); + } + + public void addPermanentEnchantmentForDev(String enchantID, int rank) { + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID); + if (apa == null) + return; + + DbManager.EnchantmentQueries.CREATE_ENCHANTMENT_FOR_ITEM((long) this.getObjectUUID(), enchantID, rank); + apa.applyEffectForItem(this, rank); + this.effectNames.add(enchantID); + } + + protected int calcMagicValue() { + int ret = 0; + for (String enchant : this.effectNames) { + ret += Item.getEnchantValue(enchant + 'A'); + } + return ret; + } + + public void addToCache() { + DbManager.addToCache(this); + } + + @Override + public void updateDatabase() { + //DbManager.ItemQueries.updateDatabase(this); + } + + @Override + public void runAfterLoad() { + loadEnchantments(); + bakeInStats(); + } + + + public ArrayList getEffectNames() { + return effectNames; + } + + public boolean validForItem(long flags) { + if (this.getItemBase() == null) + return false; + return this.getItemBase().validSlotFlag(flags); + } + + public boolean validForInventory(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) { + + if (origin == null || pc == null || charItemMan == null) + return false; + + if (ownerID != pc.getObjectUUID()) { + Logger.warn("Inventory Item " + this.getObjectUUID() + " not owned by Character " + charItemMan.getOwner().getObjectUUID()); + charItemMan.updateInventory(); + return false; + } + + if (!charItemMan.inventoryContains(this)) { + charItemMan.updateInventory(); + return false; + } + return true; + } + + public boolean validForBank(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) { + if (origin == null || pc == null || charItemMan == null) + return false; + + if (!charItemMan.bankContains(this)) + return false; + else if (ownerID != pc.getObjectUUID()) { + Logger.warn("Bank Item " + this.getObjectUUID() + " not owned by Character " + charItemMan.getOwner().getObjectUUID()); + return false; + } + return true; + } + + public boolean validForEquip(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) { + if (origin == null || pc == null || charItemMan == null) + return false; + + if (!charItemMan.equippedContains(this)) + return false; + else if (ownerID != pc.getObjectUUID()) { + //duped item, cleanup + Logger.warn("Duped item id " + + this.getObjectUUID() + " removed from PC " + pc.getObjectUUID() + '.'); + DeleteItemMsg deleteItemMsg = new DeleteItemMsg(this.getObjectType().ordinal(), this.getObjectUUID()); + charItemMan.cleanupDupe(this); + Dispatch dispatch = Dispatch.borrow(pc, deleteItemMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + return false; + } + return true; + } + + public boolean validForVault(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) { + if (origin == null || pc == null || charItemMan == null) + return false; + + if (pc.getAccount() == null) + return false; + + if (!pc.getAccount().getVault().contains(this)) + return false; + else if (ownerID != pc.getAccount().getObjectUUID()) { + //duped item, cleanup + Logger.warn("Duped item id " + + this.getObjectUUID() + " removed from PC " + pc.getObjectUUID() + '.'); + DeleteItemMsg deleteItemMsg = new DeleteItemMsg(this.getObjectType().ordinal(), this.getObjectUUID()); + charItemMan.cleanupDupe(this); + Dispatch dispatch = Dispatch.borrow(pc, deleteItemMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + return false; + } + return true; + } + + public long getDateToUpgrade() { + return dateToUpgrade; + } + + public void setDateToUpgrade(long dateToUpgrade) { + this.dateToUpgrade = dateToUpgrade; + } + + /** + * @return the value + */ + public int getValue() { + + if (this.value == 0) + if (this.isID()) { + return this.getMagicValue(); + } else + return this.getBaseValue(); + + return this.value; + } + + /** + * @param value the value to set + */ + public void setValue(int value) { + this.value = value; + } + + public boolean isRandom() { + return isRandom; + } + + public void setRandom(boolean isRandom) { + this.isRandom = isRandom; + } + + public boolean isCustomValue() { + if (this.value == 0) + return false; + return true; + } +} diff --git a/src/engine/objects/ItemBase.java b/src/engine/objects/ItemBase.java new file mode 100644 index 00000000..17c5a00c --- /dev/null +++ b/src/engine/objects/ItemBase.java @@ -0,0 +1,917 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.DamageType; +import engine.Enum.GameObjectType; +import engine.Enum.ItemType; +import engine.gameManager.DbManager; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +public class ItemBase { + + public static final byte GOLD_BASE_TYPE = 4; + public static ItemBase GOLD_ITEM_BASE = null; + public static int GOLD_BASE_ID = 7; + public static ArrayList AnniverseryGifts = new ArrayList<>(); + public static HashMap _itemBaseByUUID = new HashMap<>(); + public static HashMap> ItemBaseTypeMap = new HashMap<>(); + // Internal cache + private static HashMap itemHashIDMap = new HashMap<>(); + private static HashMap _IDsByNames = new HashMap<>(); + private static ArrayList _resourceList = new ArrayList<>(); + private final int uuid; + private final String name; + private float durability; + private int value; + private short weight; + private short color; + private ItemType type; + private int vendorType; + private int modTable; + private int useID; + private int hashID; + private byte useAmount; + // Armor and weapon related values + private int equipFlag; + private int restrictFlag; + private String skillRequired; + private short percentRequired; + private float slashResist; + private float crushResist; + private float pierceResist; + private float blockMod; + private short defense; + private float dexPenalty; + private float speed; + private float range; + private short minDamage; + private short maxDamage; + private String mastery; + private engine.Enum.DamageType damageType; + private boolean twoHanded; + private boolean isConsumable; + private boolean isStackable; + private int numCharges; + // Item stat modifiers + private HashMap bakedInStats = new HashMap<>(); + private HashMap usedStats = new HashMap<>(); + private float parryBonus; + private boolean isStrBased; + private ArrayList animations = new ArrayList<>(); + private ArrayList offHandAnimations = new ArrayList<>(); + private boolean autoID = false; + + /** + * ResultSet Constructor + */ + public ItemBase(ResultSet rs) throws SQLException { + + this.uuid = rs.getInt("ID"); + this.name = rs.getString("name"); + this.durability = rs.getInt("durability"); + this.value = rs.getInt("value"); + this.weight = rs.getShort("weight"); + this.color = rs.getShort("color"); + this.type = ItemType.valueOf(rs.getString("Type")); + this.useID = rs.getInt("useID"); + this.vendorType = rs.getInt("vendorType"); + this.useAmount = rs.getByte("useAmount"); + this.modTable = rs.getInt("modTable"); + this.hashID = rs.getInt("itemHashID"); + + this.isConsumable = false; + this.isStackable = false; + this.numCharges = rs.getShort("numCharges"); + + this.equipFlag = rs.getInt("equipFlag"); + this.restrictFlag = rs.getInt("restrictFlag"); + this.skillRequired = rs.getString("skillRequired"); + this.percentRequired = rs.getShort("percentRequired"); + this.slashResist = rs.getFloat("slashResist"); + this.crushResist = rs.getFloat("crushResist"); + this.pierceResist = rs.getFloat("pierceResist"); + this.blockMod = rs.getFloat("blockMod"); + this.defense = rs.getShort("defense"); + this.dexPenalty = rs.getFloat("dexPenalty"); + this.parryBonus = rs.getFloat("parryBonus"); + this.isStrBased = (rs.getInt("isStrBased") == 1); + this.speed = rs.getFloat("speed"); + this.range = rs.getFloat("range"); + this.minDamage = rs.getShort("minDamage"); + this.maxDamage = rs.getShort("maxDamage"); + + this.mastery = rs.getString("mastery"); + damageType = DamageType.valueOf(rs.getString("damageType")); + + this.twoHanded = (rs.getInt("twoHanded") == 1); + + switch (this.type) { + case RUNE: + case SCROLL: + case COMMANDROD: + case POTION: + case TEARS: + case GUILDCHARTER: + case DEED: + case CONTRACT: + case WATERBUCKET: + case REALMCHARTER: + case GIFT: + this.isConsumable = true; + break; + case OFFERING: + this.isConsumable = true; + Boon.HandleBoonListsForItemBase(uuid); + break; + case RESOURCE: + this.isStackable = true; + break; + + } + + this.autoIDItemsCheck(); + + try { + DbManager.ItemBaseQueries.LOAD_ANIMATIONS(this); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + initBakedInStats(); + initializeHashes(); + + } + + public static void addToCache(ItemBase itemBase) { + + _itemBaseByUUID.put(itemBase.uuid, itemBase); + + if (itemBase.type.equals(ItemType.RESOURCE)) + _resourceList.add(itemBase); + + _IDsByNames.put(itemBase.name.toLowerCase().replace(" ", "_"), itemBase.uuid); + } + + public static HashMap getItemHashIDMap() { + return itemHashIDMap; + } + + /* + * Database + */ + public static ItemBase getItemBase(int uuid) { + + return _itemBaseByUUID.get(uuid); + } + + /** + * Get the ItemBase instance for Gold. + * + * @return ItemBase for Gold + */ + public static ItemBase getGoldItemBase() { + if (ItemBase.GOLD_ITEM_BASE == null) + ItemBase.GOLD_ITEM_BASE = getItemBase(7); + return ItemBase.GOLD_ITEM_BASE; + } + + public static int getIDByName(String name) { + if (ItemBase._IDsByNames.containsKey(name)) + return ItemBase._IDsByNames.get(name); + return 0; + } + + /** + * @return the _itemBaseByUUID + */ + public static HashMap getUUIDCache() { + return _itemBaseByUUID; + } + + /** + * @return the _resourceList + */ + public static ArrayList getResourceList() { + return _resourceList; + } + + public static void loadAllItemBases() { + DbManager.ItemBaseQueries.LOAD_ALL_ITEMBASES(); + AnniverseryGifts.add(971000); + AnniverseryGifts.add(971001); + AnniverseryGifts.add(971002); + AnniverseryGifts.add(971003); + AnniverseryGifts.add(971004); + AnniverseryGifts.add(971005); + AnniverseryGifts.add(971006); + AnniverseryGifts.add(971007); + AnniverseryGifts.add(971008); + AnniverseryGifts.add(971009); + AnniverseryGifts.add(971010); + AnniverseryGifts.add(5101000); + AnniverseryGifts.add(5101020); + AnniverseryGifts.add(5101100); + AnniverseryGifts.add(5101120); + AnniverseryGifts.add(5101040); + AnniverseryGifts.add(5101140); + AnniverseryGifts.add(5101060); + AnniverseryGifts.add(5101080); + + + } + + /* + * Getters + */ + public String getName() { + return this.name; + } + + public float getDurability() { + return this.durability; + } + + private void initBakedInStats() { + DbManager.ItemBaseQueries.LOAD_BAKEDINSTATS(this); + } + + //TODO fix this later. Shouldn't be gotten from item base + public int getMagicValue() { + return this.value; + } + + public int getBaseValue() { + return this.value; + } + + public short getWeight() { + return this.weight; + } + + public int getColor() { + return this.color; + } + + public boolean isConsumable() { + return this.isConsumable; + } + + public boolean isStackable() { + return this.isStackable; + } + + public int getNumCharges() { + + return this.numCharges; + + } + + public int getEquipFlag() { + + if ((this.type == ItemType.ARMOR) + || (this.type == ItemType.WEAPON) + || (this.type == ItemType.JEWELRY)) + return this.equipFlag; + else + return 0; + } + + public boolean isRune() { + int ID = uuid; + if (ID > 2499 && ID < 3050) //class, discipline runes + return true; + else + return ID > 249999 && ID < 252137; + } + + public boolean isStatRune() { + int ID = uuid; + return ID > 249999 && ID < 250045; + } + + public boolean isDiscRune() { + int ID = uuid; + if (ID > 2499 && ID < 3050) { //class, discipline runes + return true; + } else { + return false; + } + } + + public boolean isGlass() { + int ID = uuid; + return ID > 7000099 && ID < 7000281; + } + + + public boolean isMasteryRune() { + int ID = uuid; + if (ID > 250114 && ID < 252128) + switch (ID) { + case 250115: + case 250118: + case 250119: + case 250120: + case 250121: + case 250122: + case 252123: + case 252124: + case 252125: + case 252126: + case 252127: + return true; + default: + return false; + } + return false; + } + + //returns powers tokens baked in to item + public HashMap getBakedInStats() { + return this.bakedInStats; + } + + //returns power tokens granted when using item, such as scrolls and potions + public HashMap getUsedStats() { + return this.usedStats; + } + + public final void initializeHashes() { + itemHashIDMap.put(this.hashID, uuid); + + } + + public ItemType getType() { + return this.type; + } + + public int getUseID() { + return this.useID; + } + + public byte getUseAmount() { + return this.useAmount; + } + + public int getModTable() { + return modTable; + } + + public int getVendorType() { + return vendorType; + } + + public void setVendorType(int vendorType) { + this.vendorType = vendorType; + } + + public int getHashID() { + return hashID; + } + + public void setHashID(int hashID) { + this.hashID = hashID; + } + + private void autoIDItemsCheck() { + //AUto ID Vorg and Glass + switch (uuid) { + + case 27550: + case 27560: + case 27580: + case 27590: + case 188500: + case 188510: + case 188520: + case 188530: + case 188540: + case 188550: + case 189100: + case 189110: + case 189120: + case 189130: + case 189140: + case 189150: + case 189510: + case 27600: + case 181840: + case 188700: + case 188720: + case 189550: + case 189560: + case 7000100: + case 7000110: + case 7000120: + case 7000130: + case 7000140: + case 7000150: + case 7000160: + case 7000170: + case 7000180: + case 7000190: + case 7000200: + case 7000210: + case 7000220: + case 7000230: + case 7000240: + case 7000250: + case 7000270: + case 7000280: + this.autoID = true; + break; + default: + this.autoID = false; + } + } + + public boolean validForSkills(ConcurrentHashMap skills) { + + CharacterSkill characterSkill; + + if (this.skillRequired.isEmpty()) + return true; + + characterSkill = skills.get(this.skillRequired); + + if (characterSkill == null) + return false; + + return !(this.percentRequired > characterSkill.getModifiedAmountBeforeMods()); + } + + public boolean canEquip(int slot, CharacterItemManager itemManager, AbstractCharacter abstractCharacter, Item item) { + + if (itemManager == null || abstractCharacter == null) + return false; + + if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + if (!validForSlot(slot, itemManager.getEquipped(), item)) + return false; + + if (!validForSkills(abstractCharacter.getSkills())) + return false; + + return item.getItemBase().value != 0 || Kit.IsNoobGear(item.getItemBase().uuid); + //players can't wear 0 value items. + + } + + return true; //Mobiles and NPC's don't need to check equip + } + + public int getValidSlot() { + int slotValue = 0; + + switch (this.type) { + case WEAPON: + if ((this.equipFlag & 1) != 0) + slotValue = MBServerStatics.SLOT_MAINHAND; + else if ((this.equipFlag & 2) != 0) + slotValue = MBServerStatics.SLOT_OFFHAND; + break; + case ARMOR: + if ((this.equipFlag & 2) != 0) + slotValue = MBServerStatics.SLOT_OFFHAND; + else if ((this.equipFlag & 4) != 0) + slotValue = MBServerStatics.SLOT_HELMET; + else if ((this.equipFlag & 8) != 0) + slotValue = MBServerStatics.SLOT_CHEST; + else if ((this.equipFlag & 16) != 0) + slotValue = MBServerStatics.SLOT_ARMS; + else if ((this.equipFlag & 32) != 0) + slotValue = MBServerStatics.SLOT_GLOVES; + else if ((this.equipFlag & 64) != 0) + slotValue = MBServerStatics.SLOT_RING2; + else if ((this.equipFlag & 128) != 0) + slotValue = MBServerStatics.SLOT_RING1; + else if ((this.equipFlag & 256) != 0) + slotValue = MBServerStatics.SLOT_NECKLACE; + else if ((this.equipFlag & 512) != 0) + slotValue = MBServerStatics.SLOT_LEGGINGS; + else if ((this.equipFlag & 1024) != 0) + slotValue = MBServerStatics.SLOT_FEET; + break; + + case HAIR: + if (this.equipFlag == 131072) + slotValue = MBServerStatics.SLOT_HAIRSTYLE; + else if (this.equipFlag == 65536) + slotValue = MBServerStatics.SLOT_BEARDSTYLE; + break; + + } + return slotValue; + + } + + public boolean validSlotFlag(long flags) { + + boolean validSlot = false; + + switch (this.type) { + case WEAPON: + if (this.isMelee()) + validSlot = ((flags & 1) != 0); + else if (this.isThrowing()) + validSlot = ((flags & 2) != 0); + else if (this.isArchery()) + validSlot = ((flags & 4) != 0); + else if (this.isScepter()) + validSlot = ((flags & 8) != 0); + else if (this.isStaff()) + validSlot = ((flags & 16) != 0); + break; + case JEWELRY: + if (this.isNecklace()) + validSlot = ((flags & 2147483648L) != 0L); + else + validSlot = ((flags & 4294967296L) != 0L); + break; + case ARMOR: + + if (this.isShield()) { + validSlot = ((flags & 32) != 0); + break; + } + + if (this.isClothArmor()) { + + if (this.getEquipFlag() == 4) //hood + validSlot = ((flags & 64) != 0); + else if (this.getEquipFlag() == 8) { + if ((restrictFlag & 512) != 0) //Robe + validSlot = ((flags & 128) != 0); + else + validSlot = ((flags & 1024) != 0); //Tunic/Shirt + + break; + } else if (this.getEquipFlag() == 16) //Sleeves + validSlot = ((flags & 2048) != 0); + else if (this.getEquipFlag() == 32) //Gloves + validSlot = ((flags & 512) != 0); + else if (this.getEquipFlag() == 512) //Pants + validSlot = ((flags & 4096) != 0); + else if (this.getEquipFlag() == 1024) //Boots + validSlot = ((flags & 256) != 0); + + break; + } + + if (this.isLightArmor()) { + if (this.getEquipFlag() == 4) //helm + validSlot = ((flags & 8192) != 0); + else if (this.getEquipFlag() == 8) //Chest + validSlot = ((flags & 16384) != 0); + else if (this.getEquipFlag() == 16) //Sleeves + validSlot = ((flags & 32768) != 0); + else if (this.getEquipFlag() == 32) //Gloves + validSlot = ((flags & 65536) != 0); + else if (this.getEquipFlag() == 512) //Pants + validSlot = ((flags & 131072) != 0); + else if (this.getEquipFlag() == 1024) //Boots + validSlot = ((flags & 262144) != 0); + + break; + } + + if (this.isMediumArmor()) { + if (this.getEquipFlag() == 4) //helm + validSlot = ((flags & 524288) != 0); + else if (this.getEquipFlag() == 8) //Chest + validSlot = ((flags & 1048576) != 0); + else if (this.getEquipFlag() == 16) //Sleeves + validSlot = ((flags & 2097152) != 0); + else if (this.getEquipFlag() == 32) //Gloves + validSlot = ((flags & 4194304) != 0); + else if (this.getEquipFlag() == 512) //Pants + validSlot = ((flags & 8388608) != 0); + else if (this.getEquipFlag() == 1024) //Boots + validSlot = ((flags & 16777216) != 0); + + break; + } + + if (this.isHeavyArmor()) + if (this.getEquipFlag() == 4) //helm + validSlot = ((flags & 33554432) != 0); + else if (this.getEquipFlag() == 8) //Chest + validSlot = ((flags & 67108864) != 0); + else if (this.getEquipFlag() == 16) //Sleeves + validSlot = ((flags & 134217728) != 0); + else if (this.getEquipFlag() == 32) //Gloves + validSlot = ((flags & 268435456) != 0); + else if (this.getEquipFlag() == 512) //Pants + validSlot = ((flags & 536870912) != 0); + else if (this.getEquipFlag() == 1024) //Boots + validSlot = ((flags & 1073741824) != 0); + break; + } + return validSlot; + } + + public boolean validForSlot(int slot, ConcurrentHashMap equipped, Item item) { + + boolean validSlot = false; + + if (equipped == null) + return validSlot; + + // Cannot equip an item in a slot already taken + if (equipped.get(slot) != null && equipped.get(slot).equals(item) == false) + return validSlot; + + switch (item.getItemBase().type) { + case WEAPON: + + // Only two slots available for weapons + if ((slot != MBServerStatics.SLOT_MAINHAND) && (slot != MBServerStatics.SLOT_OFFHAND)) + break; + + //make sure weapon is valid for slot + if ((slot & this.equipFlag) == 0) + break; + + // Two handed weapons take up two slots + if ((this.twoHanded == true) && + ((slot == MBServerStatics.SLOT_OFFHAND && equipped.get(MBServerStatics.SLOT_MAINHAND) != null) || + (slot == MBServerStatics.SLOT_MAINHAND && equipped.get(MBServerStatics.SLOT_OFFHAND) != null))) + break; + + // Validation passed, must be a valid weapon + + validSlot = true; + break; + case JEWELRY: + // Not a valid slot for ring + + if (this.isRing() && + ((slot != MBServerStatics.SLOT_RING1) && (slot != MBServerStatics.SLOT_RING2))) + break; + + // Not a valid slot for necklace + + if (this.isNecklace() && slot != MBServerStatics.SLOT_NECKLACE) + break; + + // Passed validation, must be valid bling bling + + validSlot = true; + break; + case ARMOR: + + // Invalid slot for armor? + if (slot == MBServerStatics.SLOT_OFFHAND && ((2 & this.equipFlag) == 0)) + break; + if (slot == MBServerStatics.SLOT_HELMET && ((4 & this.equipFlag) == 0)) + break; + if (slot == MBServerStatics.SLOT_CHEST && ((8 & this.equipFlag) == 0)) + break; + if (slot == MBServerStatics.SLOT_ARMS && ((16 & this.equipFlag) == 0)) + break; + if (slot == MBServerStatics.SLOT_GLOVES && ((32 & this.equipFlag) == 0)) + break; + if (slot == MBServerStatics.SLOT_LEGGINGS && ((512 & this.equipFlag) == 0)) + break; + if (slot == MBServerStatics.SLOT_FEET && ((1024 & this.equipFlag) == 0)) + break; + + // Is slot for this piece already taken? + if (((this.restrictFlag & 2) != 0) && (equipped.get(MBServerStatics.SLOT_OFFHAND) != null) && slot != MBServerStatics.SLOT_OFFHAND) + break; + if (((this.restrictFlag & 4) != 0) && (equipped.get(MBServerStatics.SLOT_HELMET) != null) && slot != MBServerStatics.SLOT_HELMET) + break; + if (((this.restrictFlag & 8) != 0) && (equipped.get(MBServerStatics.SLOT_CHEST) != null) && slot != MBServerStatics.SLOT_CHEST) + break; + if (((this.restrictFlag & 16) != 0) && (equipped.get(MBServerStatics.SLOT_ARMS) != null) && slot != MBServerStatics.SLOT_ARMS) + break; + if (((this.restrictFlag & 32) != 0) && (equipped.get(MBServerStatics.SLOT_GLOVES) != null) && slot != MBServerStatics.SLOT_GLOVES) + break; + if (((this.restrictFlag & 512) != 0) && (equipped.get(MBServerStatics.SLOT_LEGGINGS) != null) && slot != MBServerStatics.SLOT_LEGGINGS) + break; + if (((this.restrictFlag & 1024) != 0) && (equipped.get(MBServerStatics.SLOT_FEET) != null) && slot != MBServerStatics.SLOT_FEET) + break; + + // Passed validation. Is a valid armor piece + + validSlot = true; + break; + } + return validSlot; + } + + /** + * @return the uuid + */ + public final int getUUID() { + return uuid; + } + + public boolean isRing() { + return ((this.equipFlag & (64 | 128 | 192)) != 0); + } + + public boolean isNecklace() { + return (this.equipFlag == 256); + } + + public boolean isShield() { + return this.type.equals(ItemType.ARMOR) && this.equipFlag == 2; + } + + public boolean isLightArmor() { + return this.skillRequired.equals("Wear Armor, Light"); + } + + public boolean isMediumArmor() { + return this.skillRequired.equals("Wear Armor, Medium"); + } + + public boolean isHeavyArmor() { + return this.skillRequired.equals("Wear Armor, Heavy"); + } + + public boolean isClothArmor() { + return this.skillRequired.isEmpty(); + } + + public boolean isThrowing() { + return this.mastery.equals("Throwing") ? true : false; + } + + public boolean isStaff() { + return this.mastery.equals("Staff") ? true : false; + } + + public boolean isScepter() { + return this.mastery.equals("Benediction") ? true : false; + } + + public boolean isArchery() { + return this.mastery.equals("Archery") ? true : false; + } + + public boolean isMelee() { + return (this.isThrowing() == false && this.isStaff() == false && this.isScepter() == false && this.isArchery() == false); + } + + public boolean isTwoHanded() { + return this.twoHanded; + } + + /** + * @return the restrictFlag + */ + public int getRestrictFlag() { + return restrictFlag; + } + + /** + * @return the slashResist + */ + public float getSlashResist() { + return slashResist; + } + + /** + * @return the crushResist + */ + public float getCrushResist() { + return crushResist; + } + + /** + * @return the pierceResist + */ + public float getPierceResist() { + return pierceResist; + } + + /** + * @return the skillRequired + */ + public String getSkillRequired() { + return skillRequired; + } + + /** + * @return the mastery + */ + public String getMastery() { + return mastery; + } + + /** + * @return the blockMod + */ + public float getBlockMod() { + return blockMod; + } + + /** + * @return the defense + */ + public short getDefense() { + return defense; + } + + /** + * @return the dexPenalty + */ + public float getDexPenalty() { + return dexPenalty; + } + + /** + * @return the speed + */ + public float getSpeed() { + return speed; + } + + /** + * @return the range + */ + public float getRange() { + return range; + } + + /** + * @return the isStrBased + */ + public boolean isStrBased() { + return isStrBased; + } + + /** + * @return the parryBonus + */ + public float getParryBonus() { + return parryBonus; + } + + /** + * @return the maxDamage + */ + public short getMaxDamage() { + return maxDamage; + } + + /** + * @return the minDamage + */ + public short getMinDamage() { + return minDamage; + } + + /** + * @return the damageType + */ + public engine.Enum.DamageType getDamageType() { + return damageType; + } + + public short getPercentRequired() { + return percentRequired; + } + + public ArrayList getAnimations() { + return animations; + } + + public void setAnimations(ArrayList animations) { + this.animations = animations; + } + + public ArrayList getOffHandAnimations() { + return offHandAnimations; + } + + public void setOffHandAnimations(ArrayList offHandAnimations) { + this.offHandAnimations = offHandAnimations; + } + + public boolean isAutoID() { + return autoID; + } + + public void setAutoID(boolean autoID) { + this.autoID = autoID; + } +} diff --git a/src/engine/objects/ItemContainer.java b/src/engine/objects/ItemContainer.java new file mode 100644 index 00000000..db984558 --- /dev/null +++ b/src/engine/objects/ItemContainer.java @@ -0,0 +1,102 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.ContainerType; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; + + +public class ItemContainer extends AbstractGameObject { + + private AbstractWorldObject owner; + private ConcurrentHashMap itemMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + private ContainerType containerType; + + /** + * No Table ID Constructor + */ + public ItemContainer(AbstractWorldObject owner, ContainerType containerType) { + super(); + this.owner = owner; + this.containerType = containerType; + } + + /** + * Normal Constructor + */ + public ItemContainer(AbstractWorldObject owner, ContainerType containerType, int newUUID) { + super(newUUID); + this.owner = owner; + this.containerType = containerType; + } + + /** + * ResultSet Constructor + */ + public ItemContainer(ResultSet rs) throws SQLException { + super(rs); + + //get owner + long ownerID = rs.getLong("parent"); + this.owner = (AbstractWorldObject) AbstractGameObject.getFromTypeAndID(ownerID); + + //get ContainerType + String ct = rs.getString("container_type"); + try { + this.containerType = ContainerType.valueOf(ct.toUpperCase()); + } catch (Exception e) { + this.containerType = ContainerType.INVENTORY; + Logger.error("invalid containerType"); + } + } + + /* + * Getters + */ + public AbstractWorldObject getOwner() { + return this.owner; + } + + public ConcurrentHashMap getItemMap() { + return this.itemMap; + } + + public ContainerType getContainerType() { + return this.containerType; + } + + public boolean isBank() { + return (this.containerType == ContainerType.BANK); + } + + public boolean isInventory() { + return (this.containerType == ContainerType.INVENTORY); + } + + public boolean isVault() { + return (this.containerType == ContainerType.VAULT); + } + + public boolean containsItem(long itemID) { + return this.itemMap.containsKey(itemID); + } + + @Override + public void updateDatabase() { + // TODO Create update logic. + } + +} diff --git a/src/engine/objects/ItemFactory.java b/src/engine/objects/ItemFactory.java new file mode 100644 index 00000000..34b06cb0 --- /dev/null +++ b/src/engine/objects/ItemFactory.java @@ -0,0 +1,1050 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.ItemContainerType; +import engine.Enum.ItemType; +import engine.Enum.OwnerType; +import engine.gameManager.*; +import engine.loot.ModTableEntry; +import engine.loot.ModTypeTableEntry; +import engine.net.ItemProductionManager; +import engine.net.ItemQueue; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ErrorPopupMsg; +import engine.powers.EffectsBase; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class ItemFactory { + + public static void fillInventory(PlayerCharacter pc, int objectID, int count) { + + if (pc == null) + return; + + int max = 20; + CharacterItemManager itemManager = pc.getCharItemManager(); + ItemBase ib = ItemBase.getItemBase(objectID); + if (count > max) + count = max; + + ClientConnection cc = pc.getClientConnection(); + + if (itemManager == null || ib == null || cc == null) + return; + + boolean worked; + for (int i = 0; i < count; i++) { + worked = false; + + if (!itemManager.hasRoomInventory(ib.getWeight())) { + if (pc != null) + ChatManager.chatSystemInfo(pc, "You can not carry any more of that item."); + break; + } + + Item item = new Item(ib, pc.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0, + (short) 1, (short) 1, true, false, ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + worked = true; + } catch (Exception e) { + Logger.error(e); + } + if (worked) { + itemManager.addItemToInventory(item); + } + } + itemManager.updateInventory(); + } + + public static Item fillForge(NPC npc, PlayerCharacter pc, int itemsToRoll, int itemID, int pToken, int sToken, String customName) { + + String prefixString = ""; + String suffixString = ""; + if (npc == null) + return null; + + boolean useWarehouse = false; + + ItemBase ib = ItemBase.getItemBase(itemID); + + if (ib == null) + return null; + + Building forge = npc.getBuilding(); + + if (forge == null) + return null; + + + if (!npc.getCharItemManager().hasRoomInventory(ib.getWeight())) { + if (pc != null) + ErrorPopupMsg.sendErrorPopup(pc, 21); + return null; + } + + Zone zone = npc.getBuilding().getParentZone(); + + if (zone == null) + return null; + + City city = City.getCity(zone.getPlayerCityUUID()); + + if (city == null) + return null; + MobLoot ml = null; + city.transactionLock.writeLock().lock(); + + try { + Warehouse cityWarehouse = city.getWarehouse(); + + if (cityWarehouse != null && forge.assetIsProtected()) + useWarehouse = true; + // ROLL BANE SCROLL. + + if (ib.getUUID() > 910010 && ib.getUUID() < 910019) { + ConcurrentHashMap resources = cityWarehouse.getResources(); + + + int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, ib.getBaseValue()); + int overdraft = BuildingManager.GetOverdraft(forge, ib.getBaseValue()); + + if (overdraft > 0 && !useWarehouse) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + " " + ib.getName()); + return null; + } + + if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + " " + ib.getName()); + return null; + } + + if (overdraft > resources.get(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + " " + ib.getName()); + return null; + } + + //All checks passed, lets withdraw from building first. + + // if (pc != null){ + // ChatManager.chatGuildInfo(pc.getGuild(), "Building withdraw = " + buildingWithdraw); + // ChatManager.chatGuildInfo(pc.getGuild(), "Warehouse overdraft withdraw = " + overdraft); + // + // ChatManager.chatGuildInfo(pc.getGuild(), "total withdraw = " + (overdraft + buildingWithdraw)); + // } + + if (!forge.transferGold(-buildingWithdraw, false)) { + overdraft += buildingWithdraw; + + if (!useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item." + ib.getName()); + return null; + } else { + if (overdraft > resources.get(ItemBase.GOLD_ITEM_BASE)) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item." + ib.getName()); + return null; + } + } + } + + if (overdraft > 0) + if (!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false, true)) { + //ChatManager.chatGuildError(pc, "Failed to create Item"); + Logger.error("Warehouse With UID of " + cityWarehouse.getUID() + " Failed to Create Item." + ib.getName()); + return null; + } + + + ml = new MobLoot(npc, ib, false); + + + ml.containerType = Enum.ItemContainerType.FORGE; + ml.setValue(0); + ml.loadEnchantments(); + + float time; + float rank = npc.getBuilding().getRank() - 1; + float rate = (float) (2.5 * rank); + time = (20 - rate); + time *= MBServerStatics.ONE_MINUTE; + + if (ml.getItemBase().getUUID() > 910010 && ml.getItemBase().getUUID() < 910019) { + rank = ml.getItemBaseID() - 910010; + time = rank * 60 * 60 * 3 * 1000; + + } + + // No job is submitted, as object's upgradetime field + // is used to determin whether or not an object has + // compelted rolling. The game object exists previously + // to this, not when 'compelte' is pressed. + long upgradeTime = System.currentTimeMillis() + (long) (time * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue())); + + DateTime dateTime = new DateTime(); + dateTime = dateTime.withMillis(upgradeTime); + ml.setDateToUpgrade(upgradeTime); + + npc.addItemToForge(ml); + + int playerID = 0; + + if (pc != null) + playerID = pc.getObjectUUID(); + DbManager.NPCQueries.ADD_TO_PRODUCTION_LIST(ml.getObjectUUID(), npc.getObjectUUID(), ml.getItemBaseID(), dateTime, "", "", "", false, playerID); + ProducedItem pi = new ProducedItem(ml.getObjectUUID(), npc.getObjectUUID(), ml.getItemBaseID(), dateTime, false, "", "", "", playerID); + pi.setProducedItemID(ml.getObjectUUID()); + pi.setAmount(itemsToRoll); + pi.setRandom(false); + + ItemQueue produced = ItemQueue.borrow(pi, (long) (time * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()))); + ItemProductionManager.send(produced); + + return ml; + } + + + int galvorAmount = 0; + int wormwoodAmount = 0; + int prefixCost = 0; + int suffixCost = 0; + + + if (ib.getType() == ItemType.WEAPON && ib.getPercentRequired() == 110) { + switch (ib.getSkillRequired()) { + case "Bow": + case "Crossbow": + case "Spear": + case "Pole Arm": + case "Staff": + wormwoodAmount = 20; + break; + case "Axe": + case "Dagger": + case "Sword": + case "Hammer": + case "Unarmed Combat": + + if (ib.isTwoHanded()) + galvorAmount = 20; + else + galvorAmount = 10; + break; + } + } + + ItemBase galvor = ItemBase.getItemBase(1580017); + ItemBase wormwood = ItemBase.getItemBase(1580018); + + if (galvorAmount > 0 || wormwoodAmount > 0) + if (!useWarehouse) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "This item requires resources to roll! Please make sure the forge is protected to access the warehouse." + ib.getName()); + return null; + + } + + if (galvorAmount > 0) { + if (cityWarehouse.isResourceLocked(galvor)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Galvor is locked." + ib.getName()); + return null; + } + + if (cityWarehouse.getResources().get(galvor) < galvorAmount) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Galvor in warehouse to roll this item." + ib.getName()); + return null; + } + } + + if (wormwoodAmount > 0) { + if (cityWarehouse.isResourceLocked(wormwood)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Wormwood is locked." + ib.getName()); + return null; + } + + if (cityWarehouse.getResources().get(wormwood) < wormwoodAmount) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Wormwood in warehouse to roll this item." + ib.getName()); + return null; + } + } + ConcurrentHashMap suffixResourceCosts = null; + ConcurrentHashMap prefixResourceCosts = null; + EffectsBase prefix = null; + if (pToken != 0) { + + if (!useWarehouse) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Forge cannot access warehouse! Check to make sure forge is protected." + ib.getName()); + return null; + } + prefix = PowersManager.getEffectByToken(pToken); + if (prefix == null) + return null; + EffectsBase prefixValue = PowersManager.getEffectByIDString(prefix.getIDString() + 'A'); + if (prefixValue == null) + return null; + + int baseCost = ib.getBaseValue(); + int effectCost = (int) prefixValue.getValue(); + int total = baseCost * 10 + effectCost; + + prefixCost = effectCost; + int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, total); + int overdraft = BuildingManager.GetOverdraft(forge, total); + + if (overdraft > 0 && !useWarehouse) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + ib.getName()); + return null; + } + + if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + ib.getName()); + return null; + } + + if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + ib.getName()); + return null; + } + prefixResourceCosts = prefix.getResourcesForEffect(); + for (ItemBase ibResources : prefixResourceCosts.keySet()) { + int warehouseAmount = cityWarehouse.getResources().get(ibResources); + int creationAmount = prefixResourceCosts.get(ibResources); + //ChatManager.chatInfoError(pc, "Prefix : " + ibResources.getName() + " / " + creationAmount); + if (warehouseAmount < creationAmount) { + //ChatManager.chatInfoError(pc, "You need at least " + creationAmount + " " + ibResources.getName() + " to Create this item."); + return null; + } + + } + } + + EffectsBase suffix = null; + if (sToken != 0) { + + if (!useWarehouse) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Forge cannot access warehouse! Check to make sure forge is protected." + ib.getName()); + return null; + } + suffix = PowersManager.getEffectByToken(sToken); + if (suffix == null) + return null; + EffectsBase suffixValue = PowersManager.getEffectByIDString(suffix.getIDString() + 'A'); + if (suffixValue == null) + return null; + suffixResourceCosts = suffix.getResourcesForEffect(); + int baseCost = ib.getBaseValue(); + int effectCost = (int) suffixValue.getValue(); + suffixCost = effectCost; + int total = baseCost * 10 + effectCost; + + + // int buildingWithdraw = Building.GetWithdrawAmountForRolling(forge, total); + int overdraft = BuildingManager.GetOverdraft(forge, total); + + if (overdraft > 0 && !useWarehouse) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + ib.getName()); + return null; + } + + if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + ib.getName()); + return null; + } + + if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + ib.getName()); + return null; + } + + + for (ItemBase ibResources : suffixResourceCosts.keySet()) { + int warehouseAmount = cityWarehouse.getResources().get(ibResources); + int creationAmount = suffixResourceCosts.get(ibResources); + if (warehouseAmount < creationAmount) { + // if (pc != null) + // ChatManager.chatInfoError(pc, "You need at least " + creationAmount + " " + ibResources.getName() + " to Create this item."); + return null; + } + + + } + + } + + + //Check if Total suffix and prefix costs + itemCost can be withdrawn. + int costToCreate = suffixCost + prefixCost + (ib.getBaseValue()); + int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, costToCreate); + + int overdraft = BuildingManager.GetOverdraft(forge, costToCreate); + + if (overdraft > 0 && !useWarehouse) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + ib.getName()); + return null; + } + + if (overdraft > 0 && useWarehouse && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + ib.getName()); + return null; + } + + if (useWarehouse && overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + ib.getName()); + return null; + } + + // if (pc != null){ + // ChatManager.chatGuildInfo(pc.getGuild(), "Building withdraw = " + buildingWithdraw); + // ChatManager.chatGuildInfo(pc.getGuild(), "Warehouse overdraft withdraw = " + overdraft); + // + // ChatManager.chatGuildInfo(pc.getGuild(), "total withdraw = " + (overdraft + buildingWithdraw)); + // } + + if (!forge.transferGold(-buildingWithdraw, false)) { + overdraft += buildingWithdraw; + + if (!useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item." + ib.getName()); + return null; + } else { + if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item." + ib.getName()); + return null; + } + } + } + + if (overdraft > 0 && useWarehouse) + if (!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false, true)) { + //ChatManager.chatGuildError(pc, "Failed to create Item"); + Logger.error("Warehouse With UID of " + cityWarehouse.getUID() + " Failed to Create Item." + ib.getName()); + return null; + } + + + if (prefix != null) { + + if (!useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Cannot Resource Roll without access to the warehouse! Make sure the forge is currently protected." + ib.getName()); + return null; + } + + + for (ItemBase ibResources : prefixResourceCosts.keySet()) { + + int creationAmount = prefixResourceCosts.get(ibResources); + + if (cityWarehouse.isResourceLocked(ibResources) == true) + return null; + + int oldAmount = cityWarehouse.getResources().get(ibResources); + int amount = creationAmount; + + if (oldAmount < amount) + amount = oldAmount; + + if (!cityWarehouse.withdraw(npc, ibResources, amount, false, true)) { + //ChatManager.chatGuildError(pc, "Failed to create Item"); + Logger.error("Warehouse With UID of " + cityWarehouse.getUID() + " Failed to Create Item." + ib.getName()); + return null; + } + } + } + + if (suffix != null) { + + for (ItemBase ibResources : suffixResourceCosts.keySet()) { + int creationAmount = suffixResourceCosts.get(ibResources); + + if (cityWarehouse.isResourceLocked(ibResources) == true) { + ChatManager.chatSystemError(pc, ibResources.getName() + " is locked!" + ib.getName()); + return null; + } + + int oldAmount = cityWarehouse.getResources().get(ibResources); + int amount = creationAmount; + if (oldAmount < amount) + amount = oldAmount; + if (!cityWarehouse.withdraw(npc, ibResources, amount, false, true)) { + //ChatManager.chatGuildError(pc, "Failed to create Item"); + Logger.error("Warehouse With UID of " + cityWarehouse.getUID() + " Failed to Create Item." + ib.getName()); + return null; + } + } + } + + if (prefix == null && suffix == null) { + + int baseCost = ib.getBaseValue(); + int total = (int) (baseCost + baseCost * (float) .10); + + buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, total); + + overdraft = BuildingManager.GetOverdraft(forge, total); + + if (overdraft > 0 && !useWarehouse) { + + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + ib.getName()); + return null; + } + + if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)) { + + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + ib.getName()); + return null; + } + + if (useWarehouse && overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) { + + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + ib.getName()); + return null; + } + } + + if (!forge.transferGold(-buildingWithdraw, false)) { + overdraft += buildingWithdraw; + + if (!useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item." + ib.getName()); + return null; + } else { + if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item." + ib.getName()); + return null; + } + } + } + + if (overdraft > 0) + if (!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false, true)) { + //ChatManager.chatGuildError(pc, "Failed to create Item"); + Logger.error("Warehouse With UID of " + cityWarehouse.getUID() + " Failed to Create Item." + ib.getName()); + return null; + } + + // ChatManager.chatGuildInfo(pc, "Gold Cost = " + total); + + if (galvorAmount > 0) { + if (!cityWarehouse.withdraw(npc, galvor, galvorAmount, false, true)) { + ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Galvor from warehouse!" + ib.getName()); + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + return null; + } + } + + if (wormwoodAmount > 0) { + if (!cityWarehouse.withdraw(npc, wormwood, wormwoodAmount, false, true)) { + ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Wormwood from warehouse!" + ib.getName()); + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + return null; + } + } + + ml = new MobLoot(npc, ib, false); + + ml.containerType = Enum.ItemContainerType.FORGE; + ml.setName(customName); + + if (prefix != null) { + ml.addPermanentEnchantment(prefix.getIDString(), 0, 0, true); + ml.setPrefix(prefix.getIDString()); + prefixString = prefix.getIDString(); + } + + if (suffix != null) { + ml.addPermanentEnchantment(suffix.getIDString(), 0, 0, false); + ml.setSuffix(suffix.getIDString()); + suffixString = suffix.getIDString(); + } + + ml.loadEnchantments(); + //set value to 0 so magicvalue can be recalculated in getValue. + ml.setValue(0); + + + float time; + float rank = npc.getBuilding().getRank() - 1; + float rate = (float) (2.5 * rank); + time = (20 - rate); + time *= MBServerStatics.ONE_MINUTE; + + if (ml.getItemBase().getUUID() > 910010 && ml.getItemBase().getUUID() < 910019) { + rank = ml.getItemBaseID() - 910010; + time = rank * 60 * 60 * 3 * 1000; + } + + + // No job is submitted, as object's upgradetime field + // is used to determin whether or not an object has + // compelted rolling. The game object exists previously + // to this, not when 'compelte' is pressed. + long upgradeTime = System.currentTimeMillis() + (long) (time * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue())); + + DateTime dateTime = new DateTime(); + dateTime = dateTime.withMillis(upgradeTime); + ml.setDateToUpgrade(upgradeTime); + + npc.addItemToForge(ml); + int playerID = 0; + + if (pc != null) + playerID = pc.getObjectUUID(); + + DbManager.NPCQueries.ADD_TO_PRODUCTION_LIST(ml.getObjectUUID(), npc.getObjectUUID(), ml.getItemBaseID(), dateTime, prefixString, suffixString, ml.getCustomName(), false, playerID); + ProducedItem pi = new ProducedItem(npc.getRolling().size(), npc.getObjectUUID(), ml.getItemBaseID(), dateTime, false, prefixString, suffixString, ml.getCustomName(), playerID); + pi.setProducedItemID(ml.getObjectUUID()); + pi.setAmount(itemsToRoll); + + ItemQueue produced = ItemQueue.borrow(pi, (long) (time * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()))); + ItemProductionManager.send(produced); + } catch (Exception e) { + Logger.error(e); + } finally { + city.transactionLock.writeLock().unlock(); + } + + + // npc.addItemToForge(item); + return ml; + + } + + + public static Item randomRoll(NPC vendor, PlayerCharacter playerCharacter, int itemsToRoll, int itemBaseID) { + byte itemModTable; + int prefixMod = 0; + int suffixMod = 0; + String prefix = ""; + String suffix = ""; + + ModTableEntry prefixEntry = null; + ModTableEntry suffixEntry = null; + + ItemBase ib = ItemBase.getItemBase(itemBaseID); + + if (ib == null) + return null; + + if (!vendor.getCharItemManager().hasRoomInventory(ib.getWeight())) { + + if (playerCharacter != null) + ChatManager.chatSystemInfo(playerCharacter, vendor.getName() + " " + vendor.getContract().getName() + " Inventory is full."); + + return null; + } + + itemModTable = (byte) ib.getModTable(); + + if (!vendor.getItemModTable().contains(itemModTable)) { + + if (playerCharacter != null) + ErrorPopupMsg.sendErrorPopup(playerCharacter, 59); + + return null; + } + + for (byte temp : vendor.getItemModTable()) { + + if (itemModTable != temp) + continue; + + prefixMod = vendor.getModTypeTable().get(vendor.getItemModTable().indexOf(temp)); + suffixMod = vendor.getModSuffixTable().get(vendor.getItemModTable().indexOf(temp)); + } + + if (prefixMod == 0 && suffixMod == 0) { + Logger.info("Failed to find modTables for item " + ib.getName()); + return null; + } + + // Roll on the tables for this vendor + + ModTypeTableEntry prefixTypeTable = ModTypeTableEntry.rollTable(prefixMod, ThreadLocalRandom.current().nextInt(1, 100 + 1)); + ModTypeTableEntry suffixTypeTable = ModTypeTableEntry.rollTable(suffixMod, ThreadLocalRandom.current().nextInt(1, 100 + 1)); + + // Sanity check. + + if (prefixTypeTable == null || suffixTypeTable == null) + return null; + + int rollPrefix = ThreadLocalRandom.current().nextInt(1, 100 + 1); + + if (rollPrefix < 80) { + + int randomPrefix = LootManager.TableRoll(vendor.getLevel(), false); + prefixEntry = ModTableEntry.rollTable(prefixTypeTable.modTableID, randomPrefix); + + if (prefixEntry != null) + prefix = prefixEntry.action; + + } + + int rollSuffix = ThreadLocalRandom.current().nextInt(1, 100 + 1); + + // Always have at least one mod on a magic rolled item. + // Suffix will be our backup plan. + + if (rollSuffix < 80 || prefixEntry == null) { + + int randomSuffix = LootManager.TableRoll(vendor.getLevel(), false); + suffixEntry = ModTableEntry.rollTable(suffixTypeTable.modTableID, randomSuffix); + + if (suffixEntry != null) + suffix = suffixEntry.action; + } + + MobLoot toRoll = ItemFactory.produceRandomRoll(vendor, playerCharacter, prefix, suffix, itemBaseID); + + if (toRoll == null) + return null; + + toRoll.setValue(0); + + float time; + float rank = vendor.getBuilding().getRank() - 1; + float rate = (float) (2.5 * rank); + time = (20 - rate); + time *= MBServerStatics.ONE_MINUTE; + + if (toRoll.getItemBase().getUUID() > 910010 && toRoll.getItemBase().getUUID() < 910019) { + rank = toRoll.getItemBaseID() - 910010; + time = rank * 60 * 60 * 3 * 1000; + } + + // No job is submitted, as object's upgradetime field + // is used to determine whether an object has + // completed rolling. The game object exists previously + // to this, not when 'complete' is pressed. + + long upgradeTime = System.currentTimeMillis() + (long) (time * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue())); + + DateTime dateTime = new DateTime(); + dateTime = dateTime.withMillis(upgradeTime); + toRoll.setDateToUpgrade(upgradeTime); + + int playerID = 0; + + if (playerCharacter != null) + playerID = playerCharacter.getObjectUUID(); + + DbManager.NPCQueries.ADD_TO_PRODUCTION_LIST(toRoll.getObjectUUID(), vendor.getObjectUUID(), toRoll.getItemBaseID(), dateTime, prefix, suffix, toRoll.getCustomName(), true, playerID); + + ProducedItem pi = new ProducedItem(toRoll.getObjectUUID(), vendor.getObjectUUID(), toRoll.getItemBaseID(), dateTime, true, prefix, suffix, toRoll.getCustomName(), playerID); + pi.setProducedItemID(toRoll.getObjectUUID()); + pi.setAmount(itemsToRoll); + ItemQueue produced = ItemQueue.borrow(pi, (long) (time * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()))); + ItemProductionManager.send(produced); + + return toRoll; + } + + public static MobLoot produceRandomRoll(NPC npc, PlayerCharacter pc, String prefixString, String suffixString, int itemID) { + + boolean useWarehouse = false; + + if (npc == null) + return null; + + ItemBase ib = ItemBase.getItemBase(itemID); + + if (ib == null) + return null; + + Building forge = npc.getBuilding(); + + if (forge == null) + return null; + + Zone zone = npc.getBuilding().getParentZone(); + + if (zone == null) + return null; + + City city = City.getCity(zone.getPlayerCityUUID()); + + if (city == null) + return null; + + MobLoot ml = null; + city.transactionLock.writeLock().lock(); + + try { + + + Warehouse cityWarehouse = city.getWarehouse(); + + if (cityWarehouse != null && forge.assetIsProtected()) + useWarehouse = true; + + ConcurrentHashMap resources = null; + + if (useWarehouse) + resources = cityWarehouse.getResources(); + + int galvorAmount = 0; + int wormwoodAmount = 0; + + if (ib.getType() == ItemType.WEAPON && ib.getPercentRequired() == 110) { + switch (ib.getSkillRequired()) { + case "Bow": + case "Crossbow": + case "Spear": + case "Pole Arm": + case "Staff": + wormwoodAmount = 22; + break; + case "Axe": + case "Dagger": + case "Sword": + case "Hammer": + case "Unarmed Combat": + + if (ib.isTwoHanded()) + galvorAmount = 22; + else + galvorAmount = 11; + break; + } + } + + ItemBase galvor = ItemBase.getItemBase(1580017); + ItemBase wormwood = ItemBase.getItemBase(1580018); + + //Cant roll 110% weapons that require resources if not allowed to use warehouse. + if (galvorAmount > 0 || wormwoodAmount > 0) + if (!useWarehouse) + return null; + + if (galvorAmount > 0) { + if (cityWarehouse.isResourceLocked(galvor)) { + ErrorPopupMsg.sendErrorMsg(pc, "Galvor is locked." + ib.getName()); + return null; + } + + if (cityWarehouse.getResources().get(galvor) < galvorAmount) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Galvor in warehouse to roll this item." + ib.getName()); + return null; + } + } + + if (wormwoodAmount > 0) { + if (cityWarehouse.isResourceLocked(wormwood)) { + ErrorPopupMsg.sendErrorMsg(pc, "Galvor is locked." + ib.getName()); + return null; + } + + if (cityWarehouse.getResources().get(wormwood) < wormwoodAmount) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Galvor in warehouse to roll this item." + ib.getName()); + return null; + } + } + + EffectsBase prefix = null; + + if (!prefixString.isEmpty()) { + prefix = PowersManager.getEffectByIDString(prefixString); + if (prefix == null) + return null; + } + + ItemBase goldIB = ItemBase.getGoldItemBase(); + + int baseCost = ib.getBaseValue(); + int total = (int) (baseCost + baseCost * .10); + + EffectsBase suffix = null; + + if (!suffixString.isEmpty()) { + suffix = PowersManager.getEffectByIDString(suffixString); + + if (suffix == null) + return null; + } + + //calculate gold costs and remove from the warehouse + if (prefix != null || suffix != null) { + int costToCreate = (int) (ib.getBaseValue() + ib.getBaseValue() * .10f); + int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, costToCreate); + int overdraft = BuildingManager.GetOverdraft(forge, costToCreate); + + if (overdraft > 0 && !useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + ib.getName()); + return null; + } + + if (useWarehouse && overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + ib.getName()); + return null; + } + + if (useWarehouse && overdraft > resources.get(goldIB)) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + ib.getName()); + return null; + } + + if (!forge.transferGold(-buildingWithdraw, false)) { + overdraft += buildingWithdraw; + + if (!useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item." + ib.getName()); + return null; + } else { + if (overdraft > resources.get(goldIB)) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item." + ib.getName()); + return null; + } + } + } + + // there was an overdraft, withdraw the rest from warehouse. + if (overdraft > 0) { + if (pc != null) { + if (!cityWarehouse.withdraw(pc, ItemBase.GOLD_ITEM_BASE, overdraft, false, true)) { + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + return null; + } + } else { + if (!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false, true)) { + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + return null; + } + } + } + } + + if (prefix == null && suffix == null) { + + int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, total); + int overdraft = BuildingManager.GetOverdraft(forge, total); + + if (overdraft > 0 && !useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + ib.getName()); + return null; + } + + if (useWarehouse && overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)) { + if (pc != null) + ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + ib.getName()); + return null; + } + + if (useWarehouse && overdraft > resources.get(goldIB)) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + ib.getName()); + return null; + } + + if (!forge.transferGold(-buildingWithdraw, false)) { + overdraft += buildingWithdraw; + + if (!useWarehouse) { + ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item." + ib.getName()); + return null; + } else { + if (overdraft > resources.get(goldIB)) { + ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item." + ib.getName()); + return null; + } + } + } + + if (overdraft > 0 && useWarehouse) { + + if (pc != null) { + if (!cityWarehouse.withdraw(pc, ItemBase.GOLD_ITEM_BASE, overdraft, false, true)) { + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + return null; + } + } else { + if (!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false, true)) { + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + return null; + } + } + } + } + + if (galvorAmount > 0 && useWarehouse) { + //ChatManager.chatGuildInfo(pc, "Withdrawing " + galvorAmount + " galvor from warehouse"); + if (!cityWarehouse.withdraw(npc, galvor, galvorAmount, false, true)) { + ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Galvor from warehouse!" + ib.getName()); + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + return null; + } + } + + if (wormwoodAmount > 0 && useWarehouse) { + //ChatManager.chatGuildInfo(pc, "Withdrawing " + wormwoodAmount + " wormwood from warehouse"); + if (!cityWarehouse.withdraw(npc, wormwood, wormwoodAmount, false, true)) { + ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Wormwood from warehouse for " + ib.getName()); + Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID() + "Failed to Withdrawl "); + + return null; + } + } + + ml = new MobLoot(npc, ib, false); + + ml.containerType = Enum.ItemContainerType.FORGE; + + if (prefix != null) { + ml.addPermanentEnchantment(prefix.getIDString(), 0, 0, true); + ml.setPrefix(prefix.getIDString()); + } + + if (suffix != null) { + ml.addPermanentEnchantment(suffix.getIDString(), 0, 0, false); + ml.setSuffix(suffix.getIDString()); + } + + ml.loadEnchantments(); + + + ml.setValue(0); + ml.setRandom(true); + npc.addItemToForge(ml); + } catch (Exception e) { + Logger.error(e); + } finally { + city.transactionLock.writeLock().unlock(); + } + return ml; + + } +} diff --git a/src/engine/objects/Kit.java b/src/engine/objects/Kit.java new file mode 100644 index 00000000..b44c4d86 --- /dev/null +++ b/src/engine/objects/Kit.java @@ -0,0 +1,427 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.ItemContainerType; +import engine.Enum.OwnerType; +import engine.gameManager.DbManager; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.net.UnknownHostException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + + +public class Kit extends AbstractGameObject { + + public static HashMap NoobGearIDS = new HashMap<>(); + public static HashMap> RaceClassIDMap = new HashMap<>(); + private final int raceBaseClassID; + private final byte kitNumber; + private final int legs; + private final int chest; + private final int feet; + private final int offhand; + private final int weapon; + + + /** + * No Table ID Constructor + */ + public Kit(int raceBaseClassID, byte kitNumber, int legs, int chest, + int feet, int offhand, int weapon) { + super(); + this.raceBaseClassID = raceBaseClassID; + this.kitNumber = kitNumber; + this.legs = legs; + this.chest = chest; + this.feet = feet; + this.offhand = offhand; + this.weapon = weapon; + } + + /** + * Normal Constructor + */ + public Kit(int raceBaseClassID, byte kitNumber, int legs, int chest, + int feet, int offhand, int weapon, int newUUID) { + super(newUUID); + this.raceBaseClassID = raceBaseClassID; + this.kitNumber = kitNumber; + this.legs = legs; + this.chest = chest; + this.feet = feet; + this.offhand = offhand; + this.weapon = weapon; + } + + /** + * RecordSet Constructor + */ + public Kit(ResultSet rs) throws SQLException, UnknownHostException { + super(rs); + + this.raceBaseClassID = rs.getInt("RaceBaseClassesID"); + this.kitNumber = rs.getByte("kitNumber"); + + this.legs = rs.getInt("legs"); + this.chest = rs.getInt("chest"); + this.feet = rs.getInt("feet"); + this.offhand = rs.getInt("offhand"); + this.weapon = rs.getInt("weapon"); + if (Kit.RaceClassIDMap.containsKey(this.raceBaseClassID)) { + Kit.RaceClassIDMap.get(this.raceBaseClassID).add(this); + } else { + ArrayList tempList = new ArrayList<>(); + tempList.add(this); + Kit.RaceClassIDMap.put(this.raceBaseClassID, tempList); + + } + + if (this.legs != 0) + Kit.NoobGearIDS.put(this.legs, true); + if (this.chest != 0) + Kit.NoobGearIDS.put(this.chest, true); + if (this.feet != 0) + Kit.NoobGearIDS.put(this.feet, true); + if (this.offhand != 0) + Kit.NoobGearIDS.put(this.offhand, true); + if (this.weapon != 0) + Kit.NoobGearIDS.put(this.weapon, true); + + } + + public static boolean IsNoobGear(int itemID) { + + return Kit.NoobGearIDS.containsKey(itemID); + + } + + /* + * Getters + */ + + private static boolean kitItemCreator(PlayerCharacter pc, int itemBase, int slot) { + ItemBase i = ItemBase.getItemBase(itemBase); + + Item temp = new Item(i, pc.getObjectUUID(), + OwnerType.PlayerCharacter, (byte) 0, (byte) 0, (short) 0, (short) 0, + false, false, ItemContainerType.EQUIPPED, (byte) slot, + new ArrayList<>(), ""); + + try { + temp = DbManager.ItemQueries.ADD_ITEM(temp); + } catch (Exception e) { + Logger.error(e); + } + + if (temp == null) { + Logger.info("Ungoof this goof, something is wrong with our kit."); + } + return true; + } + + public static int GetKitIDByRaceClass(final int raceID, final int classID) { + switch (raceID) { + case 2000: + switch (classID) { + case 2500: + return 2; + case 2501: + return 3; + case 2502: + return 4; + case 2503: + return 5; + } + case 2001: + + switch (classID) { + case 2500: + return 6; + case 2501: + return 7; + case 2502: + return 8; + case 2503: + return 9; + } + case 2002: + switch (classID) { + case 2500: + return 10; + case 2501: + return 11; + case 2502: + return 12; + + } + case 2003: + switch (classID) { + case 2500: + return 13; + case 2501: + return 14; + case 2502: + return 15; + } + case 2004: + + switch (classID) { + case 2500: + return 16; + case 2501: + return 17; + } + case 2005: + switch (classID) { + case 2500: + return 18; + case 2501: + return 19; + } + case 2006: + + switch (classID) { + case 2500: + return 20; + case 2501: + return 21; + + } + case 2008: + + switch (classID) { + case 2500: + return 22; + case 2501: + return 23; + case 2502: + return 24; + case 2503: + return 25; + } + + case 2009: + + switch (classID) { + case 2500: + return 26; + case 2501: + return 27; + case 2502: + return 28; + case 2503: + return 29; + } + case 2010: + + switch (classID) { + case 2500: + return 30; + } + + case 2011: + + switch (classID) { + case 2500: + return 31; + case 2501: + return 32; + case 2502: + return 33; + case 2503: + return 34; + } + + case 2012: + + switch (classID) { + case 2500: + return 35; + case 2501: + return 36; + case 2502: + return 37; + case 2503: + return 38; + } + + case 2013: + + switch (classID) { + case 2500: + return 39; + case 2501: + return 40; + case 2502: + return 41; + case 2503: + return 42; + } + + case 2014: + + switch (classID) { + case 2500: + return 43; + case 2501: + return 44; + case 2502: + return 45; + case 2503: + return 46; + } + + case 2015: + + switch (classID) { + case 2500: + return 47; + case 2502: + return 48; + case 2503: + return 49; + + } + + case 2016: + + switch (classID) { + case 2500: + return 50; + case 2502: + return 51; + case 2503: + return 52; + + } + + case 2017: + + switch (classID) { + case 2500: + return 53; + case 2501: + return 54; + + } + + case 2025: + + switch (classID) { + case 2500: + return 55; + case 2501: + return 56; + case 2502: + return 57; + case 2503: + return 58; + } + + case 2026: + + switch (classID) { + case 2500: + return 59; + case 2501: + return 60; + case 2502: + return 61; + case 2503: + return 62; + } + + case 2027: + + switch (classID) { + case 2500: + return 63; + } + + case 2028: + + switch (classID) { + case 2500: + return 64; + + case 2502: + return 65; + case 2503: + return 66; + } + + case 2029: + + switch (classID) { + case 2500: + return 67; + + case 2502: + return 68; + case 2503: + return 69; + } + + + } + return -1; + } + + public int getRaceBaseClassID() { + return raceBaseClassID; + } + + public byte getKitNumber() { + return kitNumber; + } + + public int getLegs() { + return legs; + } + + public int getChest() { + return chest; + } + + public int getFeet() { + return feet; + } + + public int getOffhand() { + return offhand; + } + + public int getWeapon() { + return weapon; + } + + public void equipPCwithKit(PlayerCharacter pc) { + if (weapon != 0) + kitItemCreator(pc, weapon, MBServerStatics.SLOT_MAINHAND); + if (offhand != 0) + kitItemCreator(pc, offhand, MBServerStatics.SLOT_OFFHAND); + if (chest != 0) + kitItemCreator(pc, chest, MBServerStatics.SLOT_CHEST); + if (legs != 0) + kitItemCreator(pc, legs, MBServerStatics.SLOT_LEGGINGS); + if (feet != 0) + kitItemCreator(pc, feet, MBServerStatics.SLOT_FEET); + } + + @Override + public void updateDatabase() { + // TODO Create update logic. + } +} diff --git a/src/engine/objects/LevelDefault.java b/src/engine/objects/LevelDefault.java new file mode 100644 index 00000000..60492d7e --- /dev/null +++ b/src/engine/objects/LevelDefault.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; + +public class LevelDefault { + + public static ConcurrentHashMap defaults = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public final int level; + public final float health; + public final float mana; + public final float stamina; + public final float atr; + public final float def; + public final float minDamage; + public final float maxDamage; + public final int goldMin; + public final int goldMax; + + /** + * ResultSet Constructor + */ + public LevelDefault(ResultSet rs) throws SQLException { + super(); + this.level = rs.getInt("level"); + this.health = rs.getFloat("health"); + this.mana = (float) rs.getInt("mana"); + this.stamina = (float) rs.getInt("stamina"); + this.atr = (float) rs.getInt("atr"); + this.def = (float) rs.getInt("def"); + this.minDamage = (float) rs.getInt("minDamage"); + this.maxDamage = (float) rs.getInt("maxDamage"); + this.goldMin = rs.getInt("goldMin"); + this.goldMax = rs.getInt("goldMax"); + } + + public static LevelDefault getLevelDefault(byte level) { + LevelDefault ret = null; + if (LevelDefault.defaults.containsKey(level)) + return LevelDefault.defaults.get(level); + + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM `static_npc_level_defaults` WHERE level = ?;"); + ps.setInt(1, (int) level); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + ret = new LevelDefault(rs); + LevelDefault.defaults.put(level, ret); + } + } catch (SQLException e) { + Logger.error("SQL Error number: " + e.getErrorCode() + ' ' + e.getMessage()); + } finally { + ps.release(); + } + return ret; + } +} \ No newline at end of file diff --git a/src/engine/objects/MaxSkills.java b/src/engine/objects/MaxSkills.java new file mode 100644 index 00000000..4850190e --- /dev/null +++ b/src/engine/objects/MaxSkills.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class MaxSkills { + + public static HashMap> MaxSkillsSet = new HashMap<>(); + private int runeID; + private int skillToken; + private int skillLevel; + private int maxSkillPercent; + + + /** + * ResultSet Constructor + */ + + public MaxSkills(ResultSet rs) throws SQLException { + this.runeID = rs.getInt("runeID"); + this.skillToken = rs.getInt("skillToken"); + this.skillLevel = rs.getInt("skillLevel"); + this.maxSkillPercent = rs.getInt("maxSkillPercent"); + } + + public MaxSkills(int runeID, int skillToken, int skillLevel, int maxSkillPercent) { + super(); + this.runeID = runeID; + this.skillToken = skillToken; + this.skillLevel = skillLevel; + this.maxSkillPercent = maxSkillPercent; + } + + public int getRuneID() { + return runeID; + } + + public void setRuneID(int runeID) { + this.runeID = runeID; + } + + public int getSkillLevel() { + return skillLevel; + } + + public void setSkillLevel(int skillLevel) { + this.skillLevel = skillLevel; + } + + public int getSkillToken() { + return skillToken; + } + + public void setSkillToken(int skillToken) { + this.skillToken = skillToken; + } + + public int getMaxSkillPercent() { + return maxSkillPercent; + } + + public void setMaxSkillPercent(int maxSkillPercent) { + this.maxSkillPercent = maxSkillPercent; + } +} diff --git a/src/engine/objects/MenuOption.java b/src/engine/objects/MenuOption.java new file mode 100644 index 00000000..36ea561a --- /dev/null +++ b/src/engine/objects/MenuOption.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class MenuOption extends AbstractGameObject { + + private final int menuID; + private final String message; + private final int optionID; + private final int prereq; + + /** + * ResultSet Constructor + */ + public MenuOption(ResultSet rs) throws SQLException { + super(rs); + this.menuID = rs.getInt("menuID"); + this.message = rs.getString("message"); + this.optionID = rs.getInt("optionID"); + this.prereq = rs.getInt("prereq"); + } + + /* + * Getters + */ + public int getMenuID() { + return this.menuID; + } + + public String getMessage() { + return this.message; + } + + public int getOptionID() { + return this.optionID; + } + + public int getPrereq() { + return this.prereq; + } + + /* + * Database + */ + @Override + public void updateDatabase() { + } +} diff --git a/src/engine/objects/MeshBounds.java b/src/engine/objects/MeshBounds.java new file mode 100644 index 00000000..10688e32 --- /dev/null +++ b/src/engine/objects/MeshBounds.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; +import engine.math.Bounds; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MeshBounds { + + public final float minX; + public final float minY; + public final float minZ; + public final float maxX; + public final float maxY; + public final float maxZ; + public final float radius; + public int meshID; + + public MeshBounds(ResultSet rs) throws SQLException { + + meshID = rs.getInt("meshID"); + minX = rs.getFloat("minX"); + minY = rs.getFloat("minY"); + minZ = rs.getFloat("minZ"); + maxX = rs.getFloat("maxX"); + maxY = rs.getFloat("maxY"); + maxZ = rs.getFloat("maxZ"); + float radiusX = (int) maxX; + float radiusZ = (int) maxZ; + + radius = Math.max(radiusX, radiusZ); + } + + public static void InitializeBuildingBounds() { + Bounds.meshBoundsCache = DbManager.BuildingQueries.LOAD_MESH_BOUNDS(); + } + + +} diff --git a/src/engine/objects/Mine.java b/src/engine/objects/Mine.java new file mode 100644 index 00000000..7a2a4b56 --- /dev/null +++ b/src/engine/objects/Mine.java @@ -0,0 +1,578 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.InterestManagement.WorldGrid; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.net.ByteBufferWriter; +import engine.net.client.msg.ErrorPopupMsg; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.net.UnknownHostException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +import static engine.gameManager.DbManager.MineQueries; +import static engine.gameManager.DbManager.getObject; +import static engine.math.FastMath.sqr; + +public class Mine extends AbstractGameObject { + + public static ConcurrentHashMap mineMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public static ConcurrentHashMap towerMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final String zoneName; + private final Zone parentZone; + public boolean isActive = false; + public PlayerCharacter lastClaimer; + public boolean wasClaimed = false; + // Not persisted to DB + public String guildName; + public GuildTag guildTag; + public String nationName; + public GuildTag nationTag; + private Resource production; + private Guild owningGuild; + private int flags; + private int buildingID; + private MineProduction mineType; + + /** + * ResultSet Constructor + */ + public Mine(ResultSet rs) throws SQLException, UnknownHostException { + super(rs); + + this.mineType = MineProduction.getByName(rs.getString("mine_type")); + + int ownerUID = rs.getInt("mine_ownerUID"); + this.buildingID = rs.getInt("mine_buildingUID"); + this.flags = rs.getInt("flags"); + int parent = rs.getInt("parent"); + this.parentZone = ZoneManager.getZoneByUUID(parent); + this.zoneName = this.parentZone.getParent().getName(); + + this.owningGuild = Guild.getGuild(ownerUID); + Guild nation = null; + + if (this.owningGuild.isEmptyGuild()) { + this.guildName = ""; + this.guildTag = GuildTag.ERRANT; + nation = Guild.getErrantGuild(); + this.owningGuild = Guild.getErrantGuild(); + } else { + this.guildName = this.owningGuild.getName(); + this.guildTag = this.owningGuild.getGuildTag(); + nation = this.owningGuild.getNation(); + } + + if (!nation.isEmptyGuild()) { + this.nationName = nation.getName(); + this.nationTag = nation.getGuildTag(); + } else { + this.nationName = ""; + this.nationTag = GuildTag.ERRANT; + } + + this.production = Resource.valueOf(rs.getString("mine_resource")); + this.lastClaimer = null; + + } + + public static void releaseMineClaims(PlayerCharacter playerCharacter) { + + if (playerCharacter == null) + return; + + for (Mine mine : Mine.getMines()) { + + if (mine.lastClaimer != null) + if (mine.lastClaimer.equals(playerCharacter)) { + mine.lastClaimer = null; + mine.updateGuildOwner(null); + } + + } + } + + public static void SendMineAttackMessage(Building mine) { + + if (mine.getBlueprint() == null) + return; + + if (mine.getBlueprint().getBuildingGroup() != Enum.BuildingGroup.MINE) + return; + + + if (mine.getGuild().isEmptyGuild()) + return; + + if (mine.getGuild().getNation().isEmptyGuild()) + return; + + if (mine.getTimeStamp("MineAttack") > System.currentTimeMillis()) + return; + + mine.getTimestamps().put("MineAttack", System.currentTimeMillis() + MBServerStatics.ONE_MINUTE); + + ChatManager.chatNationInfo(mine.getGuild().getNation(), mine.getName() + " in " + mine.getParentZone().getParent().getName() + " is Under attack!"); + } + + public static void loadAllMines() { + + try { + + //Load mine resources + MineProduction.addResources(); + + //pre-load all building sets + ArrayList serverMines = MineQueries.GET_ALL_MINES_FOR_SERVER(); + + for (Mine mine : serverMines) { + Mine.mineMap.put(mine, mine.buildingID); + Mine.towerMap.put(mine.buildingID, mine); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /* + * Getters + */ + + public static Mine getMineFromTower(int towerID) { + return Mine.towerMap.get(towerID); + } + + public static void serializeForClientMsg(Mine mine, ByteBufferWriter writer) { + writer.putInt(mine.getObjectType().ordinal()); + writer.putInt(mine.getObjectUUID()); + writer.putInt(mine.getObjectUUID()); //actually a hash of mine + writer.putString(mine.mineType.name); + writer.putString(mine.zoneName); + writer.putInt(mine.production.hash); + writer.putInt(mine.production.baseProduction); + writer.putInt(mine.getModifiedProductionAmount()); //TODO calculate range penalty here + writer.putInt(3600); //window in seconds + + // Errant mines are currently open. Set time to now. + + LocalDateTime mineOpenTime = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); + + // Mine times are those of the nation not individual guild. + + Guild mineNatonGuild = mine.getOwningGuild().getNation(); + + // Adjust the serialized mine time based upon whether + // the Guild's mine window has passed or not and if it was claimed. + // If a mine is active serialize current datetime irrespective + // of any claim. + + if (mineNatonGuild.isEmptyGuild() == false && mine.isActive == false) { + + int guildWOO = mineNatonGuild.getNation().getMineTime(); + LocalDateTime guildMineTime = mineOpenTime.withHour(guildWOO); + + if (mineOpenTime.isAfter(guildMineTime) || mine.wasClaimed == true) + mineOpenTime = guildMineTime.plusDays(1); + else + mineOpenTime = guildMineTime; + + } + + writer.putLocalDateTime(mineOpenTime); + writer.putLocalDateTime(mineOpenTime.plusHours(1)); + writer.put(mine.isActive ? (byte) 0x01 : (byte) 0x00); + + Building mineTower = BuildingManager.getBuilding(mine.buildingID); + writer.putFloat(mineTower.getLoc().x); + writer.putFloat(mineTower.getParentZone().getLoc().y); + writer.putFloat(mineTower.getLoc().z); + + writer.putInt(mine.isExpansion() ? mine.mineType.xpacHash : mine.mineType.hash); + + writer.putString(mine.guildName); + GuildTag._serializeForDisplay(mine.guildTag, writer); + writer.putString(mine.nationName); + GuildTag._serializeForDisplay(mine.nationTag, writer); + } + + public static ArrayList getMinesForGuild(int guildID) { + + ArrayList mineList = new ArrayList<>(); + + // Only inactive mines are returned. + + for (Mine mine : Mine.mineMap.keySet()) { + if (mine.owningGuild.getObjectUUID() == guildID && + mine.isActive == false) + mineList.add(mine); + } + return mineList; + } + + /* + * Database + */ + public static Mine getMine(int UID) { + return MineQueries.GET_MINE(UID); + + } + + public static ArrayList getMines() { + return new ArrayList<>(mineMap.keySet()); + } + + public static boolean validateClaimer(PlayerCharacter playerCharacter) { + + // Method validates that the claimer meets + // all the requirements to claim; landed + // guild with a warehouse, etc. + + Guild playerGuild; + + //verify the player exists + + if (playerCharacter == null) + return false; + + //verify the player is in valid guild + + playerGuild = playerCharacter.getGuild(); + + // Can't claim something if you don't have a guild! + + if (playerGuild.isEmptyGuild()) + return false; + + if (playerGuild.getNation().isEmptyGuild()) + return false; + + // Guild must own a city to hold a mine. + + City guildCity = playerGuild.getOwnedCity(); + + if (guildCity == null) + return false; + + if (guildCity.getWarehouse() == null) { + ErrorPopupMsg.sendErrorMsg(playerCharacter, "No Warehouse exists for this claim."); + return false; + } + + // Number of mines is based on the rank of the nation's tree. + + City nationCapitol = playerGuild.getNation().getOwnedCity(); + + Building nationCapitolTOL = nationCapitol.getTOL(); + + if (nationCapitolTOL == null) + return false; + + int treeRank = nationCapitolTOL.getRank(); + + if (treeRank < 1) + return false; + + if (guildUnderMineLimit(playerGuild.getNation(), treeRank) == false) { + ErrorPopupMsg.sendErrorMsg(playerCharacter, "Your nation cannot support another mine."); + return false; + } + + return true; + } + + private static boolean guildUnderMineLimit(Guild playerGuild, int tolRank) { + + int mineCnt = 0; + + mineCnt += Mine.getMinesForGuild(playerGuild.getObjectUUID()).size(); + + for (Guild guild : playerGuild.getSubGuildList()) + mineCnt += Mine.getMinesForGuild(guild.getObjectUUID()).size(); + + return mineCnt <= tolRank; + } + + public boolean changeProductionType(Resource resource) { + if (!this.validForMine(resource)) + return false; + //update resource in database; + if (!MineQueries.CHANGE_RESOURCE(this, resource)) + return false; + + this.production = resource; + return true; + } + + public MineProduction getMineType() { + return this.mineType; + } + + public void setMineType(String type) { + this.mineType = MineProduction.getByName(type); + } + + public String getZoneName() { + return this.zoneName; + } + + public Resource getProduction() { + return this.production; + } + + public boolean getIsActive() { + return this.isActive; + } + + public Guild getOwningGuild() { + if (this.owningGuild == null) + return Guild.getErrantGuild(); + else + return this.owningGuild; + } + + public void setOwningGuild(Guild owningGuild) { + this.owningGuild = owningGuild; + } + + /* + * Serialization + */ + + public int getFlags() { + return flags; + } + + public void setFlags(int flags) { + this.flags = flags; + } + + public Zone getParentZone() { + return parentZone; + } + + public GuildTag getGuildTag() { + return guildTag; + } + + public void setActive(boolean isAc) { + + this.isActive = isAc; + Building building = BuildingManager.getBuildingFromCache(this.buildingID); + if (building != null && !this.isActive) + building.isDeranking.compareAndSet(true, false); + } + + public boolean validForMine(Resource r) { + if (this.mineType == null) + return false; + return this.mineType.validForMine(r, this.isExpansion()); + } + + public void serializeForMineProduction(ByteBufferWriter writer) { + writer.putInt(this.getObjectType().ordinal()); + writer.putInt(this.getObjectUUID()); + writer.putInt(this.getObjectUUID()); //actually a hash of mine + // writer.putInt(0x215C92BB); //this.unknown1); + writer.putString(this.mineType.name); + writer.putString(this.zoneName); + writer.putInt(this.production.hash); + writer.putInt(this.production.baseProduction); + writer.putInt(this.getModifiedProductionAmount()); //TODO calculate range penalty here + writer.putInt(3600); //window in seconds + writer.putInt(this.isExpansion() ? this.mineType.xpacHash : this.mineType.hash); + } + + @Override + public void updateDatabase() { + // TODO Create update logic. + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public void handleDestroyMine() { + + if (!this.isActive) + return; + + //remove tags from mine + + this.guildName = ""; + this.nationName = ""; + this.owningGuild = Guild.getErrantGuild(); + this.lastClaimer = null; + this.wasClaimed = false; + + // Update database + + DbManager.MineQueries.CHANGE_OWNER(this, 0); + + // Update mesh + + Building mineBuilding = BuildingManager.getBuildingFromCache(this.buildingID); + + if (mineBuilding == null) { + Logger.debug("Null mine building " + this.getObjectUUID() + ". Unable to Load Building with UID " + this.buildingID); + return; + } + + mineBuilding.setOwner(null); + mineBuilding.refresh(false); + + // remove hirelings + + Building building = (Building) getObject(Enum.GameObjectType.Building, this.buildingID); + BuildingManager.cleanupHirelings(building); + } + + public boolean claimMine(PlayerCharacter claimer) { + + if (claimer == null) + return false; + + if (!validateClaimer(claimer)) + return false; + + if (!this.isActive) { + ErrorPopupMsg.sendErrorMsg(claimer, "Can not for to claim inactive mine."); + return false; + } + + if (!updateGuildOwner(claimer)) + return false; + + // Successful claim + + this.lastClaimer = claimer; + + return true; + } + + public boolean depositMineResources() { + + if (this.owningGuild.isEmptyGuild()) + return false; + + if (this.owningGuild.getOwnedCity() == null) + return false; + + if (this.owningGuild.getOwnedCity().getWarehouse() == null) + return false; + + ItemBase resourceIB = ItemBase.getItemBase(this.production.UUID); + return this.owningGuild.getOwnedCity().getWarehouse().depositFromMine(this, resourceIB, this.getModifiedProductionAmount()); + } + + public boolean updateGuildOwner(PlayerCharacter playerCharacter) { + + Building mineBuilding = BuildingManager.getBuildingFromCache(this.buildingID); + + //should never return null, but let's check just in case. + + if (mineBuilding == null) { + ChatManager.chatSystemError(playerCharacter, "Unable to find mine tower."); + Logger.debug("Failed to Update Mine with UID " + this.getObjectUUID() + ". Unable to Load Building with UID " + this.buildingID); + return false; + } + + if (playerCharacter == null) { + this.owningGuild = Guild.getErrantGuild(); + this.guildName = "None"; + this.guildTag = GuildTag.ERRANT; + this.nationName = "None"; + this.nationTag = GuildTag.ERRANT; + //Update Building. + mineBuilding.setOwner(null); + WorldGrid.updateObject(mineBuilding); + return true; + } + + Guild guild = playerCharacter.getGuild(); + + if (guild.getOwnedCity() == null) + return false; + + if (!MineQueries.CHANGE_OWNER(this, guild.getObjectUUID())) { + Logger.debug("Database failed to Change Ownership of Mine with UID " + this.getObjectUUID()); + ChatManager.chatSystemError(playerCharacter, "Failed to claim Mine."); + return false; + } + + //update mine. + this.owningGuild = guild; + + //Update Building. + PlayerCharacter guildLeader = (PlayerCharacter) Guild.GetGL(this.owningGuild); + + if (guildLeader != null) + mineBuilding.setOwner(guildLeader); + WorldGrid.updateObject(mineBuilding); + return true; + } + + public boolean isExpansion() { + return (this.flags & 2) != 0; + } + + public int getModifiedProductionAmount() { + //TODO Calculate Distance modifications. + + //calculate base values. + int baseProduction = this.production.baseProduction; + float baseModValue = this.production.baseProduction * .1f; + float rankModValue = this.production.baseProduction * .0143f; + float totalModded = 0; + + //get Mine Building. + Building mineBuilding = BuildingManager.getBuilding(this.buildingID); + if (mineBuilding == null) + return this.production.baseProduction; + for (AbstractCharacter harvester : mineBuilding.getHirelings().keySet()) { + totalModded += baseModValue; + totalModded += rankModValue * harvester.getRank(); + } + //add base production on top; + totalModded += baseProduction; + //skip distance check for expansion. + if (this.isExpansion()) + return (int) totalModded; + + if (this.owningGuild.isEmptyGuild() == false) { + if (this.owningGuild.getOwnedCity() != null) { + float distanceSquared = this.owningGuild.getOwnedCity().getLoc().distanceSquared2D(mineBuilding.getLoc()); + + if (distanceSquared > sqr(10000 * 3)) + totalModded *= .25f; + else if (distanceSquared > sqr(10000 * 2)) + totalModded *= .50f; + else if (distanceSquared > sqr(10000)) + totalModded *= .75f; + } + } + return (int) totalModded; + } + +} diff --git a/src/engine/objects/MineProduction.java b/src/engine/objects/MineProduction.java new file mode 100644 index 00000000..0aac80a0 --- /dev/null +++ b/src/engine/objects/MineProduction.java @@ -0,0 +1,94 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.util.HashMap; + +public enum MineProduction { + + LUMBER("Lumber Camp", new HashMap<>(), Resource.WORMWOOD, 1618637196, 1663491950), + ORE("Ore Mine", new HashMap<>(), Resource.OBSIDIAN, 518103023, -788976428), + GOLD("Gold Mine", new HashMap<>(), Resource.GALVOR, -662193002, -1227205358), + MAGIC("Magic Mine", new HashMap<>(), Resource.BLOODSTONE, 504746863, -1753567069); + + public final String name; + public final HashMap resources; + public final Resource xpac; + public final int hash; + public final int xpacHash; + + MineProduction(String name, HashMap resources, Resource xpac, int hash, int xpacHash) { + this.name = name; + this.resources = resources; + this.xpac = xpac; + this.hash = hash; + this.xpacHash = xpacHash; + } + + public static void addResources() { + if (MineProduction.LUMBER.resources.size() == 0) { + MineProduction.LUMBER.resources.put(7, Resource.GOLD); + MineProduction.LUMBER.resources.put(1580004, Resource.LUMBER); + MineProduction.LUMBER.resources.put(1580005, Resource.OAK); + MineProduction.LUMBER.resources.put(1580006, Resource.BRONZEWOOD); + MineProduction.LUMBER.resources.put(1580007, Resource.MANDRAKE); + } + if (MineProduction.ORE.resources.size() == 0) { + MineProduction.ORE.resources.put(7, Resource.GOLD); + MineProduction.ORE.resources.put(1580000, Resource.STONE); + MineProduction.ORE.resources.put(1580001, Resource.TRUESTEEL); + MineProduction.ORE.resources.put(1580002, Resource.IRON); + MineProduction.ORE.resources.put(1580003, Resource.ADAMANT); + } + if (MineProduction.GOLD.resources.size() == 0) { + MineProduction.GOLD.resources.put(7, Resource.GOLD); + MineProduction.GOLD.resources.put(1580000, Resource.STONE); + MineProduction.GOLD.resources.put(1580008, Resource.COAL); + MineProduction.GOLD.resources.put(1580009, Resource.AGATE); + MineProduction.GOLD.resources.put(1580010, Resource.DIAMOND); + MineProduction.GOLD.resources.put(1580011, Resource.ONYX); + } + if (MineProduction.MAGIC.resources.size() == 0) { + MineProduction.MAGIC.resources.put(7, Resource.GOLD); + MineProduction.MAGIC.resources.put(1580012, Resource.AZOTH); + MineProduction.MAGIC.resources.put(1580013, Resource.ORICHALK); + MineProduction.MAGIC.resources.put(1580014, Resource.ANTIMONY); + MineProduction.MAGIC.resources.put(1580015, Resource.SULFUR); + MineProduction.MAGIC.resources.put(1580016, Resource.QUICKSILVER); + } + } + + public static MineProduction getByName(String name) { + if (name.toLowerCase().equals("lumber")) + return MineProduction.LUMBER; + else if (name.toLowerCase().equals("ore")) + return MineProduction.ORE; + else if (name.toLowerCase().equals("gold")) + return MineProduction.GOLD; + else + return MineProduction.MAGIC; + } + + public boolean validForMine(Resource r, boolean isXpac) { + if (r == null) + return false; + if (this.resources.containsKey(r.UUID)) + return true; + else + return isXpac && r.UUID == this.xpac.UUID; + } + + +//Name Xpac Resources +//Lumber Camp Wormwood Gold, Lumber, Oak, Bronzewood, Mandrake +//Ore Mine Obsidian Gold, Stone, Truesteal, Iron, Adamant +//Gold Mine Galvor Gold, Coal, Agate, Diamond, Onyx +//Magic Mine Bloodstone Gold, Orichalk, Azoth, Antimony, Quicksilver, Sulfer +} diff --git a/src/engine/objects/Mob.java b/src/engine/objects/Mob.java new file mode 100644 index 00000000..70602b02 --- /dev/null +++ b/src/engine/objects/Mob.java @@ -0,0 +1,2267 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import ch.claude_martin.enumbitset.EnumBitSet; +import engine.Enum; +import engine.Enum.*; +import engine.InterestManagement.WorldGrid; +import engine.exception.SerializationException; +import engine.gameManager.*; +import engine.job.JobScheduler; +import engine.jobs.DeferredPowerJob; +import engine.jobs.UpgradeNPCJob; +import engine.math.Bounds; +import engine.math.Quaternion; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.mobileAI.Threads.MobAIThread; +import engine.net.ByteBufferWriter; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.PetMsg; +import engine.net.client.msg.PlaceAssetMsg; +import engine.powers.EffectsBase; +import engine.powers.MobPowerEntry; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static engine.math.FastMath.acos; +import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup; + +public class Mob extends AbstractIntelligenceAgent { + + private static final ReentrantReadWriteLock createLock = new ReentrantReadWriteLock(); + private static final ConcurrentHashMap mobMapByDBID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + // Variables NOT to be stored in db + private static int staticID = 0; + //mob specific + public final ConcurrentHashMap playerAgroMap = new ConcurrentHashMap<>(); + public final ConcurrentHashMap siegeMinionMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + public long nextCastTime = 0; + public long nextCallForHelp = 0; + public ReentrantReadWriteLock minionLock = new ReentrantReadWriteLock(); + public boolean despawned = false; + public Vector3fImmutable destination = Vector3fImmutable.ZERO; + public Vector3fImmutable localLoc = Vector3fImmutable.ZERO; + public LinkedHashMap mobPowers = new LinkedHashMap<>(); + public MobBase mobBase; + public int spawnTime; + public Zone parentZone; + public Building building; + public boolean hasLoot = false; + public boolean isPlayerGuard = false; + public AbstractCharacter npcOwner; + public long deathTime = 0; + public int equipmentSetID = 0; + public int runeSet = 0; + public int bootySet = 0; + public EnumBitSet notEnemy; + public EnumBitSet enemy; + public MobBehaviourType BehaviourType; + public ArrayList patrolPoints; + public int lastPatrolPointIndex = 0; + public long stopPatrolTime = 0; + public City guardedCity; + protected int dbID; //the database ID + protected int loadID; + protected float spawnRadius; + //used by static mobs + protected int parentZoneID; + protected float statLat; + protected float statLon; + protected float statAlt; + private int currentID; + private int ownerUID = 0; //only used by pets + private AbstractWorldObject fearedObject = null; + private int buildingID; + private boolean isSiege = false; + private long lastAttackTime = 0; + private int lastMobPowerToken = 0; + private HashMap equip = null; + private DeferredPowerJob weaponPower; + private DateTime upgradeDateTime = null; + private boolean lootSync = false; + + + /** + * No Id Constructor + */ + public Mob(String firstName, String lastName, short statStrCurrent, short statDexCurrent, short statConCurrent, short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild, byte runningTrains, int npcType, boolean isMob, Zone parent, Building building, int contractID) { + super(firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp, sit, walk, combat, bindLoc, currentLoc, faceDir, healthCurrent, manaCurrent, stamCurrent, guild, runningTrains); + + this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET; + this.loadID = npcType; + this.mobBase = MobBase.getMobBase(loadID); + this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET; + this.parentZone = parent; + this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0; + this.building = building; + + if (building != null) + this.buildingID = building.getObjectUUID(); + else + this.buildingID = 0; + + if (contractID == 0) + this.contract = null; + else + this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID); + if (building != null && building.getOwner() != null) { + this.lastName = "the " + contract.getName(); + } + clearStatic(); + } + + /** + * Normal Constructor + */ + public Mob(String firstName, String lastName, short statStrCurrent, short statDexCurrent, short statConCurrent, short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild, byte runningTrains, int npcType, boolean isMob, Zone parent, int newUUID, Building building, int contractID) { + super(firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp, sit, walk, combat, bindLoc, currentLoc, faceDir, healthCurrent, manaCurrent, stamCurrent, guild, runningTrains, newUUID); + this.dbID = newUUID; + this.loadID = npcType; + + if (contractID == 0) + this.contract = null; + else + this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID); + + this.mobBase = MobBase.getMobBase(loadID); + this.parentZone = parent; + this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0; + this.building = building; + initializeMob(false, false, false); + clearStatic(); + } + + /** + * Pet Constructor + */ + public Mob(MobBase mobBase, Guild guild, Zone parent, short level, PlayerCharacter owner, int tableID) { + super(mobBase.getFirstName(), "", (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, level, 0, false, true, false, owner.getLoc(), owner.getLoc(), owner.getFaceDir(), (short) mobBase.getHealthMax(), (short) 0, (short) 0, guild, (byte) 0, tableID); + this.dbID = tableID; + this.loadID = mobBase.getObjectUUID(); + this.mobBase = mobBase; + this.parentZone = parent; + this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0; + this.ownerUID = owner.getObjectUUID(); + this.BehaviourType = Enum.MobBehaviourType.Pet1; + initializeMob(true, false, false); + clearStatic(); + } + + //SIEGE CONSTRUCTOR + public Mob(MobBase mobBase, Guild guild, Zone parent, short level, Vector3fImmutable loc, int tableID, boolean isPlayerGuard) { + super(mobBase.getFirstName(), "", (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, level, 0, false, true, false, loc, loc, Vector3fImmutable.ZERO, (short) mobBase.getHealthMax(), (short) 0, (short) 0, guild, (byte) 0, tableID); + this.dbID = tableID; + this.loadID = mobBase.getObjectUUID(); + this.mobBase = mobBase; + this.parentZone = parent; + this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0; + this.ownerUID = 0; + this.equip = new HashMap<>(); + initializeMob(false, true, isPlayerGuard); + clearStatic(); + } + + /** + * ResultSet Constructor + */ + public Mob(ResultSet rs) throws SQLException { + + super(rs); + + try { + this.dbID = rs.getInt(1); + //this.state = STATE.Idle; + this.loadID = rs.getInt("mob_mobbaseID"); + this.gridObjectType = GridObjectType.DYNAMIC; + this.spawnRadius = rs.getFloat("mob_spawnRadius"); + this.spawnTime = rs.getInt("mob_spawnTime"); + this.parentZone = null; + this.statLat = rs.getFloat("mob_spawnX"); + this.statAlt = rs.getFloat("mob_spawnY"); + this.statLon = rs.getFloat("mob_spawnZ"); + + this.localLoc = new Vector3fImmutable(this.statLat, this.statAlt, this.statLon); + + this.parentZoneID = rs.getInt("parent"); + this.level = (short) rs.getInt("mob_level"); + int buildingID = rs.getInt("mob_buildingID"); + + try { + this.building = BuildingManager.getBuilding(buildingID); + } catch (Exception e) { + this.building = null; + Logger.error(e.getMessage()); + } + + int contractID = rs.getInt("mob_contractID"); + + if (contractID == 0) + this.contract = null; + else + this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID); + + if (this.contract != null && NPC.ISGuardCaptain(contract.getContractID())) { + this.spawnTime = 60 * 15; + this.isPlayerGuard = true; + } + + int guildID = rs.getInt("mob_guildUID"); + + if (this.building != null) + this.guild = this.building.getGuild(); + else + this.guild = Guild.getGuild(guildID); + + if (this.guild == null) + this.guild = Guild.getErrantGuild(); + + java.util.Date sqlDateTime; + sqlDateTime = rs.getTimestamp("upgradeDate"); + + if (sqlDateTime != null) + upgradeDateTime = new DateTime(sqlDateTime); + else + upgradeDateTime = null; + + // Submit upgrade job if NPC is currently set to rank. + + if (this.upgradeDateTime != null) + Mob.submitUpgradeJob(this); + + this.mobBase = MobBase.getMobBase(loadID); + + this.setObjectTypeMask(MBServerStatics.MASK_MOB | this.getTypeMasks()); + + if (this.mobBase != null && this.spawnTime == 0) + this.spawnTime = this.mobBase.getSpawnTime(); + + this.bindLoc = new Vector3fImmutable(this.statLat, this.statAlt, this.statLon); + + this.runeSet = rs.getInt("runeSet"); + this.bootySet = rs.getInt("bootySet"); + + this.notEnemy = EnumBitSet.asEnumBitSet(rs.getLong("notEnemy"), Enum.MonsterType.class); + this.enemy = EnumBitSet.asEnumBitSet(rs.getLong("enemy"), Enum.MonsterType.class); + this.firstName = rs.getString("mob_name"); + + if (this.firstName.isEmpty()) + this.firstName = this.mobBase.getFirstName(); + + if (this.contract != null) { + this.equipmentSetID = this.contract.getEquipmentSet(); + this.lastName = this.getContract().getName(); + } else + this.equipmentSetID = rs.getInt("equipmentSet"); + + + if (rs.getString("fsm").length() > 1) + this.BehaviourType = MobBehaviourType.valueOf(rs.getString("fsm")); + + } catch (Exception e) { + Logger.error(e + " " + this.dbID); + } + + try { + initializeMob(false, false, this.isPlayerGuard); + } catch (Exception e) { + Logger.error("Mobile:" + this.dbID + ": " + e); + } + + } + + public static void serializeMobForClientMsgOtherPlayer(Mob mob, ByteBufferWriter writer) throws SerializationException { + Mob.serializeForClientMsgOtherPlayer(mob, writer); + } + + public static void serializeForClientMsgOtherPlayer(Mob mob, ByteBufferWriter writer) throws SerializationException { + writer.putInt(0); + writer.putInt(0); + + int tid = (mob.mobBase != null) ? mob.mobBase.getLoadID() : 0; + if (mob.isPet()) { + writer.putInt(2); + writer.putInt(3); + writer.putInt(0); + writer.putInt(2522); + writer.putInt(GameObjectType.NPCClassRune.ordinal()); + writer.putInt(mob.currentID); + } else if (tid == 100570) { //kur'adar + writer.putInt(3); + Mob.serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class + serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune + } else if (tid == 100962 || tid == 100965) { //Spydraxxx the Mighty, Denigo Tantric + writer.putInt(2); + serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneTwo.ordinal(), 252621); //guard rune + } else if (mob.contract != null || mob.isPlayerGuard) { + writer.putInt(3); + serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), MobBase.GetClassType(mob.getMobBaseID())); //warrior class + serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune + } else + writer.putInt(1); + + //Generate Race Rune + writer.putInt(1); + writer.putInt(0); + + if (mob.mobBase != null) + writer.putInt(mob.mobBase.getLoadID()); + else + writer.putInt(mob.loadID); + + writer.putInt(mob.getObjectType().ordinal()); + writer.putInt(mob.currentID); + + //Send Stats + writer.putInt(5); + writer.putInt(0x8AC3C0E6); //Str + writer.putInt(0); + writer.putInt(0xACB82E33); //Dex + writer.putInt(0); + writer.putInt(0xB15DC77E); //Con + writer.putInt(0); + writer.putInt(0xE07B3336); //Int + writer.putInt(0); + writer.putInt(0xFF665EC3); //Spi + writer.putInt(0); + + + writer.putString(mob.firstName); + writer.putString(mob.lastName); + + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.put((byte) 0); + writer.putInt(mob.getObjectType().ordinal()); + writer.putInt(mob.currentID); + + if (mob.mobBase != null) { + writer.putFloat(mob.mobBase.getScale()); + writer.putFloat(mob.mobBase.getScale()); + writer.putFloat(mob.mobBase.getScale()); + } else { + writer.putFloat(1.0f); + writer.putFloat(1.0f); + writer.putFloat(1.0f); + } + + writer.putVector3f(mob.getLoc()); + + //Rotation + float radians = (float) Math.acos(mob.getRot().y) * 2; + + if (mob.building != null) + if (mob.building.getBounds() != null && mob.building.getBounds().getQuaternion() != null) + radians += (mob.building.getBounds().getQuaternion()).angleY; + + writer.putFloat(radians); + + //Inventory Stuff + + writer.putInt(0); + + // get a copy of the equipped items. + + if (mob.equip != null) { + + writer.putInt(mob.equip.size()); + + for (MobEquipment me : mob.equip.values()) + MobEquipment.serializeForClientMsg(me, writer); + } else + writer.putInt(0); + + writer.putInt(mob.getRank()); + writer.putInt(mob.getLevel()); + writer.putInt(mob.getIsSittingAsInt()); //Standing + writer.putInt(mob.getIsWalkingAsInt()); //Walking + writer.putInt(mob.getIsCombatAsInt()); //Combat + writer.putInt(2); //Unknown + writer.putInt(1); //Unknown - Headlights? + writer.putInt(0); + + if (mob.building != null && mob.region != null) { + writer.putInt(mob.building.getObjectType().ordinal()); + writer.putInt(mob.building.getObjectUUID()); + } else { + writer.putInt(0); //<-Building Object Type + writer.putInt(0); //<-Building Object ID + } + + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + + writer.putInt(0); // NPC menu options + + if (mob.contract != null && mob.npcOwner == null) { + writer.put((byte) 1); + writer.putLong(0); + writer.putLong(0); + + if (mob.contract != null) + writer.putInt(mob.contract.getIconID()); + else + writer.putInt(0); //npc icon ID + + } else + writer.put((byte) 0); + + if (mob.npcOwner != null) { + writer.put((byte) 1); + writer.putInt(GameObjectType.PlayerCharacter.ordinal()); + writer.putInt(131117009); + writer.putInt(mob.npcOwner.getObjectType().ordinal()); + writer.putInt(mob.npcOwner.getObjectUUID()); + writer.putInt(8); + } else + writer.put((byte) 0); + + if (mob.isPet()) { + + writer.put((byte) 1); + + if (mob.getOwner() != null) { + writer.putInt(mob.getOwner().getObjectType().ordinal()); + writer.putInt(mob.getOwner().getObjectUUID()); + } else { + writer.putInt(0); //ownerType + writer.putInt(0); //ownerID + } + } else + writer.put((byte) 0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + if (!mob.isAlive() && !mob.isPet() && !mob.isNecroPet() && !mob.isSiege && !mob.isPlayerGuard) { + writer.putInt(0); + writer.putInt(0); + } + + writer.put((byte) 0); + Guild._serializeForClientMsg(mob.getGuild(), writer); + if (mob.mobBase != null && mob.mobBase.getObjectUUID() == 100570) { + writer.putInt(2); + writer.putInt(0x00008A2E); + writer.putInt(0x1AB84003); + } else if (mob.isSiege) { + writer.putInt(1); + writer.putInt(74620179); + } else + writer.putInt(0); + + writer.putInt(0); //0xB8400300 + writer.putInt(0); + + //TODO Guard + writer.put((byte) 0); + + writer.putFloat(mob.healthMax); + writer.putFloat(mob.health.get()); + + //TODO Peace Zone + writer.put((byte) 1); //0=show tags, 1=don't + + //DON't LOAD EFFECTS FOR DEAD MOBS. + + if (!mob.isAlive()) + writer.putInt(0); + else { + int indexPosition = writer.position(); + writer.putInt(0); //placeholder for item cnt + int total = 0; + + for (Effect eff : mob.getEffects().values()) { + if (eff.isStatic()) + continue; + if (!eff.serializeForLoad(writer)) + continue; + ++total; + } + + writer.putIntAt(total, indexPosition); + } + + // Effects + writer.put((byte) 0); + } + + private static void serializeRune(Mob mob, ByteBufferWriter writer, int type, int objectType, int runeID) { + writer.putInt(type); + writer.putInt(0); + writer.putInt(runeID); + writer.putInt(objectType); + writer.putInt(mob.currentID); + } + + public static Mob createMob(int loadID, Vector3fImmutable spawn, Guild guild, boolean isMob, Zone parent, Building building, int contractID, String pirateName, int level) { + + Mob mobWithoutID = new Mob(pirateName, "", (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, (short) 1, 0, false, false, false, spawn, spawn, Vector3fImmutable.ZERO, (short) 1, (short) 1, (short) 1, guild, (byte) 0, loadID, isMob, parent, building, contractID); + + if (mobWithoutID.mobBase == null) + return null; + + mobWithoutID.level = (short) level; + + // Parent zone is required by dbhandler. Can likely + // refactor that out and just use the id. + + mobWithoutID.parentZone = parent; + mobWithoutID.parentZoneID = parent.getObjectUUID(); + + // NPC in a Building derives position from slot + + if (mobWithoutID.building != null) + mobWithoutID.bindLoc = Vector3fImmutable.ZERO; + + Mob mob; + + try { + mob = DbManager.MobQueries.ADD_MOB(mobWithoutID); + mob.setObjectTypeMask(MBServerStatics.MASK_MOB | mob.getTypeMasks()); + + } catch (Exception e) { + Logger.error("SQLException:" + e.getMessage()); + mob = null; + } + + return mob; + } + + public static Mob createPet(int loadID, Guild guild, Zone parent, PlayerCharacter owner, short level) { + MobBase mobBase = MobBase.getMobBase(loadID); + Mob mob = null; + + if (mobBase == null || owner == null) + return null; + + createLock.writeLock().lock(); + level += 20; + + try { + mob = new Mob(mobBase, guild, parent, level, owner, 0); + if (mob.mobBase == null) + return null; + mob.runAfterLoad(); + Vector3fImmutable loc = owner.getLoc(); + DbManager.addToCache(mob); + mob.setPet(owner, true); + mob.setWalkMode(false); + + } catch (Exception e) { + Logger.error(e); + } finally { + createLock.writeLock().unlock(); + } + parent.zoneMobSet.add(mob); + mob.level = level; + mob.healthMax = mob.getMobBase().getHealthMax() * (mob.level * 0.5f); + mob.health.set(mob.healthMax); + return mob; + } + + public static Mob getMob(int id) { + + if (id == 0) + return null; + + Mob mob = (Mob) DbManager.getFromCache(GameObjectType.Mob, id); + if (mob != null) + return mob; + return DbManager.MobQueries.GET_MOB(id); + } + + public static Mob getFromCache(int id) { + + + return (Mob) DbManager.getFromCache(GameObjectType.Mob, id); + } + + public static Mob getFromCacheDBID(int id) { + if (Mob.mobMapByDBID.containsKey(id)) + return Mob.mobMapByDBID.get(id); + return null; + } + + private static float getModifiedAmount(CharacterSkill skill) { + + if (skill == null) + return 0f; + + return skill.getModifiedAmount(); + } + + public static void HandleAssistedAggro(PlayerCharacter source, PlayerCharacter target) { + + HashSet mobsInRange = WorldGrid.getObjectsInRangePartial(source, MobAIThread.AI_DROP_AGGRO_RANGE, MBServerStatics.MASK_MOB); + + for (AbstractWorldObject awo : mobsInRange) { + Mob mob = (Mob) awo; + + //Mob is not attacking anyone, skip. + if (mob.getCombatTarget() == null) + continue; + + //Mob not attacking target's target, let's not be failmu and skip this target. + if (mob.getCombatTarget() != target) + continue; + + //target is mob's combat target, LETS GO. + + if (source.getHateValue() > target.getHateValue()) + mob.setCombatTarget(source); + } + } + + public static void submitUpgradeJob(Mob mob) { + + if (mob.getUpgradeDateTime() == null) { + Logger.error("Failed to get Upgrade Date"); + return; + } + + // Submit upgrade job for future date or current instant + + if (mob.getUpgradeDateTime().isAfter(DateTime.now())) + JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), mob.getUpgradeDateTime().getMillis()); + else + JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), 0); + + } + + public static int getUpgradeTime(Mob mob) { + + if (mob.getRank() < 7) + return (mob.getRank() * 8); + + return 0; + } + + public static int getUpgradeCost(Mob mob) { + + int upgradeCost; + + upgradeCost = Integer.MAX_VALUE; + + if (mob.getRank() < 7) + return (mob.getRank() * 100650) + 21450; + + return upgradeCost; + } + + public static void setUpgradeDateTime(Mob mob, DateTime upgradeDateTime) { + + if (!DbManager.MobQueries.updateUpgradeTime(mob, upgradeDateTime)) { + Logger.error("Failed to set upgradeTime for building " + mob.currentID); + return; + } + mob.upgradeDateTime = upgradeDateTime; + } + + public static synchronized Mob createGuardMob(Mob guardCaptain, Guild guild, Zone parent, Vector3fImmutable loc, short level, String pirateName) { + + MobBase minionMobBase; + Mob mob; + int maxSlots; + + switch (guardCaptain.getRank()) { + case 3: + maxSlots = 2; + break; + case 4: + case 5: + maxSlots = 3; + break; + case 6: + maxSlots = 4; + break; + case 7: + maxSlots = 5; + break; + case 1: + case 2: + default: + maxSlots = 1; + + } + + if (guardCaptain.siegeMinionMap.size() == maxSlots) + return null; + + minionMobBase = guardCaptain.mobBase; + + if (minionMobBase == null) + return null; + + mob = new Mob(minionMobBase, guild, parent, level, new Vector3fImmutable(1, 1, 1), 0, true); + mob.setLevel(level); + if (guardCaptain.equipmentSetID != 0) + mob.equipmentSetID = guardCaptain.equipmentSetID; + + mob.runAfterLoad(); + mob.despawned = true; + mob.despawn(); + + //grab equipment and name from minionbase. + + if (guardCaptain.contract != null) { + Enum.MinionType minionType = Enum.MinionType.ContractToMinionMap.get(guardCaptain.contract.getContractID()); + if (minionType != null) { + String rank; + + if (guardCaptain.getRank() < 3) + rank = MBServerStatics.JUNIOR; + else if (guardCaptain.getRank() < 6) + rank = ""; + else if (guardCaptain.getRank() == 6) + rank = MBServerStatics.VETERAN; + else + rank = MBServerStatics.ELITE; + + mob.firstName = NPCManager.getPirateName(mob.getMobBaseID()); + mob.lastName = rank + " " + minionType.getRace() + " " + minionType.getName(); + } + } + + mob.setObjectTypeMask(MBServerStatics.MASK_MOB | mob.getTypeMasks()); + + mob.isPlayerGuard = true; + + DbManager.addToCache(mob); + + RuneBase guardRune = RuneBase.getRuneBase(252621); + + for (MobBaseEffects mbe : guardRune.getEffectsList()) { + + EffectsBase eb = PowersManager.getEffectByToken(mbe.getToken()); + + if (eb == null) { + Logger.info("EffectsBase Null for Token " + mbe.getToken()); + continue; + } + + //check to upgrade effects if needed. + if (mob.effects.containsKey(Integer.toString(eb.getUUID()))) { + if (mbe.getReqLvl() > (int) mob.level) { + continue; + } + + Effect eff = mob.effects.get(Integer.toString(eb.getUUID())); + + if (eff == null) + continue; + + //Current effect is a higher rank, dont apply. + if (eff.getTrains() > mbe.getRank()) + continue; + + //new effect is of a higher rank. remove old effect and apply new one. + eff.cancelJob(); + mob.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true); + } else { + + if (mbe.getReqLvl() > (int) mob.level) + continue; + + mob.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true); + } + } + + int slot = 0; + slot += guardCaptain.siegeMinionMap.size() + 1; + + guardCaptain.siegeMinionMap.put(mob, slot); + mob.deathTime = System.currentTimeMillis(); + mob.npcOwner = guardCaptain; + mob.spawnTime = (int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60; + mob.BehaviourType = Enum.MobBehaviourType.GuardMinion; + mob.guardedCity = guardCaptain.guardedCity; + mob.parentZone = parent; + parent.zoneMobSet.add(mob); + MovementManager.translocate(mob, guardCaptain.bindLoc, guardCaptain.region); + mob.bindLoc = guardCaptain.bindLoc; + return mob; + } + + public static synchronized Mob createSiegeMob(NPC owner, int loadID, Guild guild, Zone parent, Vector3fImmutable loc, short level) { + + MobBase minionMobBase; + Mob mob; + + if (owner.getSiegeMinionMap().size() == 3) + return null; + + minionMobBase = MobBase.getMobBase(loadID); + + if (minionMobBase == null) + return null; + + mob = new Mob(minionMobBase, guild, parent, level, new Vector3fImmutable(1, 1, 1), 0, false); + //mob.runAfterLoad(); + mob.despawned = true; + DbManager.addToCache(mob); + + mob.setObjectTypeMask(MBServerStatics.MASK_MOB | mob.getTypeMasks()); + + //mob.setMob(); + mob.setSiege(true); + + int slot = 0; + + if (!owner.getSiegeMinionMap().containsValue(1)) + slot = 1; + else if (!owner.getSiegeMinionMap().containsValue(2)) + slot = 2; + + owner.getSiegeMinionMap().put(mob, slot); + + mob.setNpcOwner(owner); + mob.BehaviourType = MobBehaviourType.Pet1; + mob.BehaviourType.canRoam = false; + return mob; + } + + private void clearStatic() { + + if (this.parentZone != null) + this.parentZone.zoneMobSet.remove(this); + + this.parentZone = null; + this.statLat = 0f; + this.statLon = 0f; + this.statAlt = 0f; + } + + private void initializeMob(boolean isPet, boolean isSiege, boolean isGuard) { + + int slot; + Vector3fImmutable slotLocation; + Quaternion slotRotation; + + if (ConfigManager.serverType.equals(ServerType.LOGINSERVER)) + return; + + // Configure parent zone adding this NPC to the + // zone collection + + this.parentZone = ZoneManager.getZoneByUUID(this.parentZoneID); + this.parentZone.zoneMobSet.remove(this); + this.parentZone.zoneMobSet.add(this); + + // Setup location for this Mobile + + this.bindLoc = new Vector3fImmutable(this.statLat, this.statAlt, this.statLon); + this.bindLoc = this.parentZone.getLoc().add(this.bindLoc); + this.loc = new Vector3fImmutable(bindLoc); + this.endLoc = new Vector3fImmutable(bindLoc); + + // Handle Mobiles within buildings + + if (this.building != null) { + + if (this.contract == null) { + + // Mobiles inside buildings are offset from it not the zone + + this.bindLoc = new Vector3fImmutable(this.statLat, this.statAlt, this.statLon); + this.bindLoc = this.building.getLoc().add(this.bindLoc); + + } else { + + // Get next available slot for this Mobile and then + // add it to the building's hireling list + + slot = BuildingManager.getAvailableSlot(building); + + if (slot == -1) + Logger.error("No available slot for Mobile: " + this.getObjectUUID()); + + building.getHirelings().put(this, slot); + + // Override bind and location for this contracted Mobile + // derived from BuildingManager slot location data. + + slotLocation = BuildingManager.getSlotLocation(building, slot).getLocation(); + + this.bindLoc = building.getLoc().add(slotLocation); + + } + + // Rotate slot position by the building rotation + + this.bindLoc = Vector3fImmutable.rotateAroundPoint(building.getLoc(), this.bindLoc, building.getBounds().getQuaternion().angleY); + + this.loc = new Vector3fImmutable(bindLoc); + this.endLoc = new Vector3fImmutable(bindLoc); + + // Rotate mobile rotation by the building's rotation + + slotRotation = new Quaternion().fromAngles(0, acos(this.getRot().y) * 2, 0); + slotRotation = slotRotation.mult(building.getBounds().getQuaternion()); + this.setRot(new Vector3f(0, slotRotation.y, 0)); + + // Configure building region and floor/level for this Mobile + + this.region = BuildingManager.GetRegion(this.building, bindLoc.x, bindLoc.y, bindLoc.z); + } + + if (this.mobBase != null) { + this.gridObjectType = GridObjectType.DYNAMIC; + this.healthMax = this.mobBase.getHealthMax(); + this.manaMax = 0; + this.staminaMax = 0; + this.setHealth(this.healthMax); + this.mana.set(this.manaMax); + this.stamina.set(this.staminaMax); + + if (isPet) + this.setObjectTypeMask(MBServerStatics.MASK_PET | this.getTypeMasks()); + + if (this.contract == null) + this.level = (short) this.mobBase.getLevel(); + } + + //set bonuses + this.bonuses = new PlayerBonuses(this); + + //TODO set these correctly later + this.rangeHandOne = 8; + this.rangeHandTwo = -1; + this.minDamageHandOne = 0; + this.maxDamageHandOne = 0; + this.minDamageHandTwo = 1; + this.maxDamageHandTwo = 4; + this.atrHandOne = 300; + this.defenseRating = (short) this.mobBase.getDefenseRating(); + this.isActive = true; + + this.charItemManager.load(); + + //load AI for general mobs. + if (this.contract != null && NPC.ISWallArcher(this.contract)) { + this.BehaviourType = MobBehaviourType.GuardWallArcher; + this.isPlayerGuard = true; + this.spawnTime = 450; + } + if (isPet || isSiege || (isGuard && this.contract == null)) + this.currentID = (--Mob.staticID); + else + this.currentID = this.dbID; + + //store mobs by Database ID + + if (!isPet && !isSiege) + Mob.mobMapByDBID.put(this.dbID, this); + + } + + /* + * Getters + */ + @Override + public int getDBID() { + return this.dbID; + } + + public int getLoadID() { + return loadID; + } + + /* + * Serialization + */ + + @Override + public int getObjectUUID() { + return currentID; + } + + public float getSpawnX() { + return this.statLat; + } + + public float getSpawnY() { + return this.statAlt; + } + + public float getSpawnZ() { + return this.statLon; + } + + public float getSpawnRadius() { + return this.spawnRadius; + } + + public void setSpawnTime(int value) { + this.spawnTime = value; + } + + //use getSpawnTime instead. This is just for init tables + public int getTrueSpawnTime() { + return this.spawnTime; + } + + public String getSpawnTimeAsString() { + if (this.spawnTime == 0) + return MBServerStatics.DEFAULT_SPAWN_TIME_MS / 1000 + " seconds (Default)"; + else + return this.spawnTime + " seconds"; + + } + + @Override + public MobBase getMobBase() { + return this.mobBase; + } + + public int getMobBaseID() { + + if (this.mobBase != null) + return this.mobBase.getObjectUUID(); + + return 0; + } + + public Vector3fImmutable getTrueBindLoc() { + return this.bindLoc; + } + + public Zone getParentZone() { + return this.parentZone; + } + + public int getParentZoneID() { + + if (this.parentZone != null) + return this.parentZone.getObjectUUID(); + + return 0; + } + + @Override + public int getGuildUUID() { + + if (this.guild == null) + return 0; + + return this.guild.getObjectUUID(); + } + + @Override + public PlayerCharacter getOwner() { + + if (!this.isPet()) + return null; + + if (this.ownerUID == 0) + return null; + + return PlayerCharacter.getFromCache(this.ownerUID); + } + + public void setOwner(PlayerCharacter value) { + + if (value == null) + this.ownerUID = 0; + else + this.ownerUID = value.getObjectUUID(); + } + + public void setFearedObject(AbstractWorldObject awo) { + this.fearedObject = awo; + } + + @Override + public Vector3fImmutable getBindLoc() { + + if (this.isPet() && !this.isSiege) + return this.getOwner() != null ? this.getOwner().getLoc() : this.getLoc(); + return this.bindLoc; + } + + public void calculateModifiedStats() { + + float strVal = this.mobBase.getMobBaseStats().getBaseStr(); + float dexVal = this.mobBase.getMobBaseStats().getBaseDex(); + float conVal = 0; // I believe this will desync the Mobs Health if we call it. + float intVal = this.mobBase.getMobBaseStats().getBaseInt(); + float spiVal = this.mobBase.getMobBaseStats().getBaseSpi(); + + // TODO modify for equipment + if (this.bonuses != null) { + // modify for effects + strVal += this.bonuses.getFloat(ModType.Attr, SourceType.Strength); + dexVal += this.bonuses.getFloat(ModType.Attr, SourceType.Dexterity); + conVal += this.bonuses.getFloat(ModType.Attr, SourceType.Constitution); + intVal += this.bonuses.getFloat(ModType.Attr, SourceType.Intelligence); + spiVal += this.bonuses.getFloat(ModType.Attr, SourceType.Spirit); + + // apply dex penalty for armor + // modify percent amounts. DO THIS LAST! + strVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Strength)); + dexVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Dexterity)); + conVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Constitution)); + intVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Intelligence)); + spiVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Spirit)); + } else { + // apply dex penalty for armor + } + + // Set current stats + this.statStrCurrent = (strVal < 1) ? (short) 1 : (short) strVal; + this.statDexCurrent = (dexVal < 1) ? (short) 1 : (short) dexVal; + this.statConCurrent = (conVal < 1) ? (short) 1 : (short) conVal; + this.statIntCurrent = (intVal < 1) ? (short) 1 : (short) intVal; + this.statSpiCurrent = (spiVal < 1) ? (short) 1 : (short) spiVal; + + } + + @Override + public float getSpeed() { + float bonus = 1; + if (this.bonuses != null) + // get rune and effect bonuses + bonus *= (1 + this.bonuses.getFloatPercentAll(ModType.Speed, SourceType.None)); + + if (this.isPlayerGuard) + switch (this.mobBase.getLoadID()) { + case 2111: + if (this.isWalk()) + if (this.isCombat()) + return Guards.HumanArcher.getWalkCombatSpeed() * bonus; + else + return Guards.HumanArcher.getWalkSpeed() * bonus; + else + return Guards.HumanArcher.getRunSpeed() * bonus; + + case 14103: + if (this.isWalk()) + if (this.isCombat()) + return Guards.UndeadArcher.getWalkCombatSpeed() * bonus; + else + return Guards.UndeadArcher.getWalkSpeed() * bonus; + else + return Guards.UndeadArcher.getRunSpeed() * bonus; + } + //return combat speeds + //not combat return normal speeds + if (this.isCombat()) + if (this.isWalk()) { + if (this.mobBase.getWalkCombat() <= 0) + return MBServerStatics.MOB_SPEED_WALKCOMBAT * bonus; + return this.mobBase.getWalkCombat() * bonus; + } else { + if (this.mobBase.getRunCombat() <= 0) + return MBServerStatics.MOB_SPEED_RUNCOMBAT * bonus; + return this.mobBase.getRunCombat() * bonus; + } + else if (this.isWalk()) { + if (this.mobBase.getWalk() <= 0) + return MBServerStatics.MOB_SPEED_WALK * bonus; + return this.mobBase.getWalk() * bonus; + } else { + if (this.mobBase.getRun() <= 0) + return MBServerStatics.MOB_SPEED_RUN * bonus; + return this.mobBase.getRun() * bonus; + } + + } + + @Override + public float getPassiveChance(String type, int AttackerLevel, boolean fromCombat) { + //TODO add this later for dodge + return 0f; + } + + /* + * Database + */ + + /** + * @ Kill this Character + */ + @Override + public void killCharacter(AbstractCharacter attacker) { + + + this.stopMovement(this.getMovementLoc()); + + if (attacker != null) + if (attacker.getObjectType() == GameObjectType.PlayerCharacter) { + Group g = GroupManager.getGroup((PlayerCharacter) attacker); + + // Give XP, now handled inside the Experience Object + if (!this.isPet() && !this.isNecroPet() && !(this.agentType.equals(AIAgentType.PET)) && !this.isPlayerGuard) + Experience.doExperience((PlayerCharacter) attacker, this, g); + } else if (attacker.getObjectType().equals(GameObjectType.Mob)) { + Mob mobAttacker = (Mob) attacker; + + if (mobAttacker.isPet()) { + + PlayerCharacter owner = mobAttacker.getOwner(); + + if (owner != null) + if (!this.isPet() && !this.isNecroPet() && !(this.agentType.equals(AIAgentType.PET)) && !this.isPlayerGuard) { + Group g = GroupManager.getGroup(owner); + + // Give XP, now handled inside the Experience Object + Experience.doExperience(owner, this, g); + } + } + } + killCleanup(); + } + + public void updateLocation() { + + if (!this.isMoving()) + return; + + if (this.isAlive() == false || this.getBonuses().getBool(ModType.Stunned, SourceType.None) || this.getBonuses().getBool(ModType.CannotMove, SourceType.None)) { + //Target is stunned or rooted. Don't move + + this.stopMovement(this.getMovementLoc()); + + return; + } + + Vector3fImmutable newLoc = this.getMovementLoc(); + + if (newLoc.equals(this.getEndLoc())) { + this.stopMovement(newLoc); + this.region = AbstractWorldObject.GetRegionByWorldObject(this); + return; + //Next upda + } + + setLoc(newLoc); + this.region = AbstractWorldObject.GetRegionByWorldObject(this); + //Next update will be end Loc, lets stop him here. + + } + + @Override + public void killCharacter(String reason) { + killCleanup(); + } + + private void killCleanup() { + Dispatch dispatch; + + try { + //resync corpses + //this.setLoc(this.getMovementLoc()); + if (this.isSiege) { + this.deathTime = System.currentTimeMillis(); + //this.state = STATE.Dead; + try { + this.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + this.setCombatTarget(null); + this.hasLoot = false; + this.playerAgroMap.clear(); + + if (this.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardMinion.ordinal()) + this.spawnTime = (int) (-2.500 * ((Mob) this.npcOwner).building.getRank() + 22.5) * 60; + + if (this.isPet()) { + + PlayerCharacter petOwner = this.getOwner(); + + if (petOwner != null) { + this.setOwner(null); + petOwner.setPet(null); + PetMsg petMsg = new PetMsg(5, null); + dispatch = Dispatch.borrow(this.getOwner(), petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } + } + + } else if (this.isPet() || this.isNecroPet()) { + //this.state = STATE.Disabled; + + this.setCombatTarget(null); + this.hasLoot = false; + + //if (this.parentZone != null) + //this.parentZone.zoneMobSet.remove(this); + ZoneManager.getSeaFloor().zoneMobSet.remove(this); + try { + this.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + this.playerAgroMap.clear(); + WorldGrid.RemoveWorldObject(this); + + DbManager.removeFromCache(this); + PlayerCharacter petOwner = this.getOwner(); + + if (petOwner != null) { + this.setOwner(null); + petOwner.setPet(null); + PetMsg petMsg = new PetMsg(5, null); + dispatch = Dispatch.borrow(petOwner, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } + } else { + + //cleanup effects + playerAgroMap.clear(); + + if (!this.isPlayerGuard && this.equip != null) + LootManager.GenerateEquipmentDrop(this); + + } + try { + this.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + this.combat = false; + this.walkMode = true; + this.setCombatTarget(null); + + this.hasLoot = this.charItemManager.getInventoryCount() > 0; + } catch (Exception e) { + Logger.error(e); + } + this.updateLocation(); + } + + public void respawn() { + + this.despawned = false; + this.setCombatTarget(null); + this.setHealth(this.healthMax); + this.stamina.set(this.staminaMax); + this.mana.set(this.manaMax); + this.combat = false; + this.walkMode = true; + this.setCombatTarget(null); + this.isAlive.set(true); + this.deathTime = 0; + this.lastBindLoc = this.bindLoc; + this.setLoc(this.lastBindLoc); + this.stopMovement(this.lastBindLoc); + + NPCManager.applyRuneSetEffects(this); + + this.recalculateStats(); + this.setHealth(this.healthMax); + + if (this.building == null && this.npcOwner != null && ((Mob) this.npcOwner).BehaviourType.ordinal() == MobBehaviourType.GuardCaptain.ordinal()) + this.building = ((Mob) this.npcOwner).building; + else if (this.building != null) + this.region = BuildingManager.GetRegion(this.building, bindLoc.x, bindLoc.y, bindLoc.z); + + //MovementManager.translocate(this, this.bindLoc, this.region); + + if (!this.isSiege && !this.isPlayerGuard && contract == null) + loadInventory(); + + this.updateLocation(); + } + + public void despawn() { + + this.despawned = true; + + WorldGrid.RemoveWorldObject(this); + this.charItemManager.clearInventory(); + } + + @Override + public boolean canBeLooted() { + return !this.isAlive(); + } + + public int getTypeMasks() { + + if (this.mobBase == null) + return 0; + + return this.mobBase.getTypeMasks(); + } + + /** + * Clears and sets the inventory of the Mob. Must be called every time the + * mob is spawned or respawned. + */ + public void loadInventory() { + + if (!MBServerStatics.ENABLE_MOB_LOOT) + return; + + this.charItemManager.clearInventory(); + this.charItemManager.clearEquip(); + + if (isPlayerGuard) + return; + + LootManager.GenerateMobLoot(this); + } + + @Override + public void updateDatabase() { + // DbManager.MobQueries.updateDatabase(this); + } + + public void refresh() { + if (this.isAlive()) + WorldGrid.updateObject(this); + } + + public void recalculateStats() { + + try { + calculateModifiedStats(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + try { + calculateAtrDefenseDamage(); + } catch (Exception e) { + Logger.error(this.getMobBaseID() + " /" + e.getMessage()); + } + try { + calculateMaxHealthManaStamina(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + Resists.calculateResists(this); + } + + public void calculateMaxHealthManaStamina() { + float h; + float m; + float s; + + h = this.mobBase.getHealthMax(); + if (this.isPet()) { + h = this.level * 0.5f * 120; + } + m = this.statSpiCurrent; + s = this.statConCurrent; + + // Apply any bonuses from runes and effects + + if (this.bonuses != null) { + h += this.bonuses.getFloat(ModType.HealthFull, SourceType.None); + m += this.bonuses.getFloat(ModType.ManaFull, SourceType.None); + s += this.bonuses.getFloat(ModType.StaminaFull, SourceType.None); + + //apply effects percent modifiers. DO THIS LAST! + + h *= (1 + this.bonuses.getFloatPercentAll(ModType.HealthFull, SourceType.None)); + m *= (1 + this.bonuses.getFloatPercentAll(ModType.ManaFull, SourceType.None)); + s *= (1 + this.bonuses.getFloatPercentAll(ModType.StaminaFull, SourceType.None)); + } + + // Set max health, mana and stamina + + if (h > 0) + this.healthMax = h; + else + this.healthMax = 1; + + if (m > -1) + this.manaMax = m; + else + this.manaMax = 0; + + if (s > -1) + this.staminaMax = s; + else + this.staminaMax = 0; + + // Update health, mana and stamina if needed + + if (this.getHealth() > this.healthMax) + this.setHealth(this.healthMax); + + if (this.mana.get() > this.manaMax) + this.mana.set(this.manaMax); + + if (this.stamina.get() > this.staminaMax) + this.stamina.set(staminaMax); + + } + + public void calculateAtrDefenseDamage() { + + if (this.charItemManager == null || this.equip == null) { + Logger.error("Player " + currentID + " missing skills or equipment"); + defaultAtrAndDamage(true); + defaultAtrAndDamage(false); + this.defenseRating = 0; + return; + } + + try { + calculateAtrDamageForWeapon(this.equip.get(MBServerStatics.SLOT_MAINHAND), true); + } catch (Exception e) { + + this.atrHandOne = (short) this.mobBase.getAttackRating(); + this.minDamageHandOne = (short) this.mobBase.getMinDmg(); + this.maxDamageHandOne = (short) this.mobBase.getMaxDmg(); + this.rangeHandOne = 6.5f; + this.speedHandOne = 20; + Logger.info("Mobbase ID " + this.getMobBaseID() + " returned an error. setting to default ATR and Damage." + e.getMessage()); + } + + try { + calculateAtrDamageForWeapon(this.equip.get(MBServerStatics.SLOT_OFFHAND), false); + + } catch (Exception e) { + + this.atrHandTwo = (short) this.mobBase.getAttackRating(); + this.minDamageHandTwo = (short) this.mobBase.getMinDmg(); + this.maxDamageHandTwo = (short) this.mobBase.getMaxDmg(); + this.rangeHandTwo = 6.5f; + this.speedHandTwo = 20; + Logger.info("Mobbase ID " + this.getMobBaseID() + " returned an error. setting to default ATR and Damage." + e.getMessage()); + } + + try { + float defense = this.mobBase.getDefenseRating(); + defense += getShieldDefense(equip.get(MBServerStatics.SLOT_OFFHAND)); + defense += getArmorDefense(equip.get(MBServerStatics.SLOT_HELMET)); + defense += getArmorDefense(equip.get(MBServerStatics.SLOT_CHEST)); + defense += getArmorDefense(equip.get(MBServerStatics.SLOT_ARMS)); + defense += getArmorDefense(equip.get(MBServerStatics.SLOT_GLOVES)); + defense += getArmorDefense(equip.get(MBServerStatics.SLOT_LEGGINGS)); + defense += getArmorDefense(equip.get(MBServerStatics.SLOT_FEET)); + defense += getWeaponDefense(equip); + + // TODO add error log here + if (this.bonuses != null) { + + // add any bonuses + + defense += (short) this.bonuses.getFloat(ModType.DCV, SourceType.None); + + // Finally, multiply any percent modifiers. DO THIS LAST! + + float pos_Bonus = 1 + this.bonuses.getFloatPercentPositive(ModType.DCV, SourceType.None); + + + defense = (short) (defense * pos_Bonus); + + //Lucky rune applies next + + float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.DCV, SourceType.None); + defense = (short) (defense * (1 + neg_Bonus)); + + + } else + Logger.error("Error: missing bonuses"); + + defense = (defense < 1) ? 1 : defense; + this.defenseRating = (short) (defense + 0.5f); + } catch (Exception e) { + Logger.info("Mobbase ID " + this.getMobBaseID() + " returned an error. Setting to Default Defense." + e.getMessage()); + this.defenseRating = (short) this.mobBase.getDefense(); + } + // calculate defense for equipment + } + + private float getWeaponDefense(HashMap equipped) { + + MobEquipment weapon = equipped.get(MBServerStatics.SLOT_MAINHAND); + ItemBase wb = null; + CharacterSkill skill, mastery; + float val = 0; + boolean unarmed = false; + + if (weapon == null) { + weapon = equipped.get(MBServerStatics.SLOT_OFFHAND); + + if (weapon == null) + unarmed = true; + else + wb = weapon.getItemBase(); + + } else + wb = weapon.getItemBase(); + + if (wb == null) + unarmed = true; + + if (unarmed) { + skill = null; + mastery = null; + } else { + skill = this.skills.get(wb.getSkillRequired()); + mastery = this.skills.get(wb.getMastery()); + } + + if (skill != null) + val += (int) skill.getModifiedAmount() / 2f; + + if (mastery != null) + val += (int) mastery.getModifiedAmount() / 2f; + + return val; + } + + private float getShieldDefense(MobEquipment shield) { + + if (shield == null) + return 0; + + ItemBase ab = shield.getItemBase(); + + if (ab == null || !ab.isShield()) + return 0; + + CharacterSkill blockSkill = this.skills.get("Block"); + float skillMod; + + if (blockSkill == null) { + skillMod = CharacterSkill.getQuickMastery(this, "Block"); + + if (skillMod == 0f) + return 0; + + } else + skillMod = blockSkill.getModifiedAmount(); + + float def = ab.getDefense(); + + //apply item defense bonuses + + return (def * (1 + ((int) skillMod / 100f))); + } + + private float getArmorDefense(MobEquipment armor) { + + if (armor == null) + return 0; + + ItemBase ib = armor.getItemBase(); + + if (ib == null) + return 0; + + if (!ib.getType().equals(ItemType.ARMOR)) + return 0; + + if (ib.getSkillRequired().isEmpty()) + return ib.getDefense(); + + CharacterSkill armorSkill = this.skills.get(ib.getSkillRequired()); + + if (armorSkill == null) + return ib.getDefense(); + + float def = ib.getDefense(); + + //apply item defense bonuses + + return (def * (1 + ((int) armorSkill.getModifiedAmount() / 50f))); + } + + private void calculateAtrDamageForWeapon(MobEquipment weapon, boolean mainHand) { + + int baseStrength = 0; + + float skillPercentage, masteryPercentage; + float mastDam; + + // make sure weapon exists + + boolean noWeapon = false; + ItemBase wb = null; + + if (weapon == null) + noWeapon = true; + + else { + + ItemBase ib = weapon.getItemBase(); + + if (ib == null) + noWeapon = true; + else if (ib.getType().equals(ItemType.WEAPON) == false) { + defaultAtrAndDamage(mainHand); + return; + } else + wb = ib; + } + + float min, max; + float speed; + boolean strBased = false; + + // get skill percentages and min and max damage for weapons + + if (noWeapon) { + + if (mainHand) + this.rangeHandOne = this.mobBase.getAttackRange(); + else + this.rangeHandTwo = -1; // set to do not attack + + skillPercentage = getModifiedAmount(this.skills.get("Unarmed Combat")); + masteryPercentage = getModifiedAmount(this.skills.get("Unarmed Combat Mastery")); + + if (masteryPercentage == 0f) + mastDam = CharacterSkill.getQuickMastery(this, "Unarmed Combat Mastery"); + else + mastDam = masteryPercentage; + + // TODO Correct these + min = this.mobBase.getMinDmg(); + max = this.mobBase.getMaxDmg(); + } else { + + if (mainHand) + this.rangeHandOne = weapon.getItemBase().getRange() * (1 + (baseStrength / 600.0f)); + else + this.rangeHandTwo = weapon.getItemBase().getRange() * (1 + (baseStrength / 600.0f)); + + skillPercentage = getModifiedAmount(this.skills.get(wb.getSkillRequired())); + masteryPercentage = getModifiedAmount(this.skills.get(wb.getMastery())); + + if (masteryPercentage == 0f) + mastDam = 0f; + else + mastDam = masteryPercentage; + + min = wb.getMinDamage(); + max = wb.getMaxDamage(); + strBased = wb.isStrBased(); + } + + // calculate atr + float atr = this.mobBase.getAttackRating(); + + if (this.statStrCurrent > this.statDexCurrent) + atr += statStrCurrent * .5; + else + atr += statDexCurrent * .5; + + // add in any bonuses to atr + + if (this.bonuses != null) { + atr += this.bonuses.getFloat(ModType.OCV, SourceType.None); + + // Finally use any multipliers. DO THIS LAST! + float pos_Bonus = 1 + this.bonuses.getFloatPercentPositive(ModType.OCV, SourceType.None); + + atr *= pos_Bonus; + + //and negative percent modifiers + //TODO DO DEBUFFS AFTER?? wILL TEst when finished + float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.OCV, SourceType.None); + + atr *= (1 + neg_Bonus); + } + + atr = (atr < 1) ? 1 : atr; + + // set atr + + if (mainHand) + this.atrHandOne = (short) (atr + 0.5f); + else + this.atrHandTwo = (short) (atr + 0.5f); + + //calculate speed + + if (wb != null) + speed = wb.getSpeed(); + else + speed = 20f; //unarmed attack speed + + if (this.bonuses != null && this.bonuses.getFloat(ModType.AttackDelay, SourceType.None) != 0f) //add effects speed bonus + speed *= (1 + this.bonuses.getFloatPercentAll(ModType.AttackDelay, SourceType.None)); + + if (speed < 10) + speed = 10; + + //add min/max damage bonuses for weapon **REMOVED + + //if duel wielding, cut damage by 30% + // calculate damage + + float minDamage; + float maxDamage; + float pri = (strBased) ? (float) this.statStrCurrent : (float) this.statDexCurrent; + float sec = (strBased) ? (float) this.statDexCurrent : (float) this.statStrCurrent; + + minDamage = (float) (min * ((0.0315f * Math.pow(pri, 0.75f)) + (0.042f * Math.pow(sec, 0.75f)) + (0.01f * ((int) skillPercentage + (int) mastDam)))); + maxDamage = (float) (max * ((0.0785f * Math.pow(pri, 0.75f)) + (0.016f * Math.pow(sec, 0.75f)) + (0.0075f * ((int) skillPercentage + (int) mastDam)))); + + minDamage = (float) ((int) (minDamage + 0.5f)); //round to nearest decimal + maxDamage = (float) ((int) (maxDamage + 0.5f)); //round to nearest decimal + + //add Base damage last. + float minDamageMod = this.mobBase.getDamageMin(); + float maxDamageMod = this.mobBase.getDamageMax(); + + minDamage += minDamageMod; + maxDamage += maxDamageMod; + + // add in any bonuses to damage + + if (this.bonuses != null) { + // Add any base bonuses + minDamage += this.bonuses.getFloat(ModType.MinDamage, SourceType.None); + maxDamage += this.bonuses.getFloat(ModType.MaxDamage, SourceType.None); + + // Finally use any multipliers. DO THIS LAST! + minDamage *= (1 + this.bonuses.getFloatPercentAll(ModType.MinDamage, SourceType.None)); + maxDamage *= (1 + this.bonuses.getFloatPercentAll(ModType.MaxDamage, SourceType.None)); + } + + // set damages + + if (mainHand) { + this.minDamageHandOne = (short) minDamage; + this.maxDamageHandOne = (short) maxDamage; + this.speedHandOne = 30; + } else { + this.minDamageHandTwo = (short) minDamage; + this.maxDamageHandTwo = (short) maxDamage; + this.speedHandTwo = 30; + } + } + + private void defaultAtrAndDamage(boolean mainHand) { + + if (mainHand) { + this.atrHandOne = 0; + this.minDamageHandOne = 0; + this.maxDamageHandOne = 0; + this.rangeHandOne = -1; + this.speedHandOne = 20; + } else { + this.atrHandTwo = 0; + this.minDamageHandTwo = 0; + this.maxDamageHandTwo = 0; + this.rangeHandTwo = -1; + this.speedHandTwo = 20; + } + } + + + public ItemBase getWeaponItemBase(boolean mainHand) { + + if (this.equipmentSetID != 0) + if (equip != null) { + MobEquipment me; + + if (mainHand) + me = equip.get(1); //mainHand + else + me = equip.get(2); //offHand + + if (me != null) { + + ItemBase ib = me.getItemBase(); + + if (ib != null) + return ib; + + } + } + MobBase mb = this.mobBase; + + if (mb != null) + if (equip != null) { + + MobEquipment me; + + if (mainHand) + me = equip.get(1); //mainHand + else + me = equip.get(2); //offHand + + if (me != null) + return me.getItemBase(); + } + return null; + } + + @Override + public void runAfterLoad() { + + // Initialize inventory + + this.charItemManager = new CharacterItemManager(this); + this.loadInventory(); + try { + if (this.equipmentSetID != 0) + this.equip = MobBase.loadEquipmentSet(this.equipmentSetID); + else + this.equip = new HashMap<>(); + + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + // Powers from mobbase + + if (PowersManager.AllMobPowers.containsKey(this.getMobBaseID())) + for (MobPowerEntry mobPowerEntry : PowersManager.AllMobPowers.get(this.getMobBaseID())) + mobPowers.put(mobPowerEntry.token, mobPowerEntry.rank); + + // Powers from contract + + if (this.contract != null && PowersManager.AllMobPowers.containsKey(this.contract.getContractID())) + for (MobPowerEntry mobPowerEntry : PowersManager.AllMobPowers.get(this.contract.getContractID())) + mobPowers.put(mobPowerEntry.token, mobPowerEntry.rank); + + if (this.equip == null) { + Logger.error("Null equipset returned for uuid " + currentID); + this.equip = new HashMap<>(0); + } + + // Combine mobbase and mob aggro arrays into one bitvector + //skip for pets + + if (this.isPet() == false && (this.agentType.equals(AIAgentType.PET)) == false && this.isNecroPet() == false) { + if (this.getMobBase().notEnemy.size() > 0) + this.notEnemy.addAll(this.getMobBase().notEnemy); + + if (this.getMobBase().enemy.size() > 0) + this.enemy.addAll(this.getMobBase().enemy); + } + + try { + NPCManager.applyRuneSetEffects(this); + recalculateStats(); + this.setHealth(this.healthMax); + + // Set bounds for this mobile + + Bounds mobBounds = Bounds.borrow(); + mobBounds.setBounds(this.getLoc()); + this.setBounds(mobBounds); + + if (this.contract != null && this.contract.getContractID() == 910) { + this.isPlayerGuard = true; + this.BehaviourType = MobBehaviourType.GuardCaptain; + this.spawnTime = 900; + this.guardedCity = ZoneManager.getCityAtLocation(this.bindLoc); + } + //assign 5 random patrol points for regular mobs + + if (!(this.agentType.equals(AIAgentType.GUARD)) && !this.isPlayerGuard() && !this.isPet() && !this.isNecroPet() && !(this.agentType.equals(AIAgentType.PET)) && !(this.agentType.equals(AIAgentType.CHARMED))) { + this.patrolPoints = new ArrayList<>(); + + for (int i = 0; i < 5; ++i) { + float patrolRadius = this.getSpawnRadius(); + + if (patrolRadius > 256) + patrolRadius = 256; + + if (patrolRadius < 60) + patrolRadius = 60; + + Vector3fImmutable newPatrolPoint = Vector3fImmutable.getRandomPointInCircle(this.getBindLoc(), patrolRadius); + this.patrolPoints.add(newPatrolPoint); + + if (i == 1) + MovementManager.translocate(this, newPatrolPoint, null); + } + } + + if (this.BehaviourType == null) + this.BehaviourType = this.getMobBase().fsm; + + if (this.isPlayerGuard() && this.contract != null) + if (NPC.ISWallArcher(this.getContract())) { + this.BehaviourType = MobBehaviourType.GuardWallArcher; + this.spawnTime = 450; + } else { + this.BehaviourType = MobBehaviourType.GuardCaptain; + this.spawnTime = 900; + this.guardedCity = ZoneManager.getCityAtLocation(this.bindLoc); + } + + this.deathTime = 0; + } catch (Exception e) { + Logger.error(e.getMessage()); + } + } + + @Override + protected ConcurrentHashMap initializePowers() { + return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + } + + public boolean canSee(PlayerCharacter target) { + return this.mobBase.getSeeInvis() >= target.getHidden(); + } + + public int getBuildingID() { + return buildingID; + } + + public void setBuildingID(int buildingID) { + this.buildingID = buildingID; + } + + public boolean isSiege() { + return isSiege; + } + + public void setSiege(boolean isSiege) { + this.isSiege = isSiege; + } + + public void setNpcOwner(AbstractCharacter npcOwner) { + this.npcOwner = npcOwner; + } + + public boolean isNecroPet() { + return this.mobBase.isNecroPet(); + } + + public void handleDirectAggro(AbstractCharacter ac) { + + if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + return; + + PlayerCharacter player = (PlayerCharacter) ac; + + if (this.getCombatTarget() == null) { + this.setCombatTarget(ac); + return; + } + + if (player.getObjectUUID() == this.getCombatTarget().getObjectUUID()) + return; + + if (this.getCombatTarget().getObjectType() == GameObjectType.PlayerCharacter) + if (ac.getHateValue() > ((PlayerCharacter) this.getCombatTarget()).getHateValue()) + this.setCombatTarget(player); + } + + public void setRank(int newRank) { + + DbManager.MobQueries.SET_PROPERTY(this, "mob_level", newRank); + this.level = (short) newRank; + + } + + public boolean isRanking() { + + return this.upgradeDateTime != null; + } + + public long getLastAttackTime() { + return lastAttackTime; + } + + public void setLastAttackTime(long lastAttackTime) { + this.lastAttackTime = lastAttackTime; + } + + public void setDeathTime(long deathTime) { + this.deathTime = deathTime; + } + + public boolean isHasLoot() { + return hasLoot; + } + + public DeferredPowerJob getWeaponPower() { + return weaponPower; + } + + public void setWeaponPower(DeferredPowerJob weaponPower) { + this.weaponPower = weaponPower; + } + + public ConcurrentHashMap getSiegeMinionMap() { + return siegeMinionMap; + } + + public DateTime getUpgradeDateTime() { + + lock.readLock().lock(); + + try { + return upgradeDateTime; + } finally { + lock.readLock().unlock(); + } + } + + public Contract getContract() { + return contract; + } + + public void setContract(Contract contract) { + this.contract = contract; + } + + public boolean isPlayerGuard() { + return isPlayerGuard; + } + + public void setPlayerGuard(boolean isPlayerGuard) { + this.isPlayerGuard = isPlayerGuard; + } + + public void setPatrolPointIndex(int patrolPointIndex) { + } + + public int getLastMobPowerToken() { + return lastMobPowerToken; + } + + public void setLastMobPowerToken(int lastMobPowerToken) { + this.lastMobPowerToken = lastMobPowerToken; + } + + + public boolean isLootSync() { + return lootSync; + } + + public void setLootSync(boolean lootSync) { + this.lootSync = lootSync; + } + + public HashMap getEquip() { + return equip; + } + + public String getNameOverride() { + return firstName + " " + lastName; + } + + public void processUpgradeMob(PlayerCharacter player) { + + lock.writeLock().lock(); + + try { + + // Cannot upgrade an npc not within a building + + if (building == null) + return; + + // Cannot upgrade an npc at max rank + + if (this.getRank() == 7) + return; + + // Cannot upgrade an npc who is currently ranking + + if (this.isRanking()) + return; + + int rankCost = Mob.getUpgradeCost(this); + + // SEND NOT ENOUGH GOLD ERROR + + if (rankCost > building.getStrongboxValue()) { + sendErrorPopup(player, 127); + return; + } + + try { + + if (!building.transferGold(-rankCost, false)) + return; + + DateTime dateToUpgrade = DateTime.now().plusHours(Mob.getUpgradeTime(this)); + Mob.setUpgradeDateTime(this, dateToUpgrade); + + // Schedule upgrade job + + Mob.submitUpgradeJob(this); + + } catch (Exception e) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + } + + } catch (Exception e) { + Logger.error(e); + } finally { + lock.writeLock().unlock(); + } + } + + public void dismiss() { + + if (this.isPet()) { + + if ((this.agentType.equals(AIAgentType.PET))) { //delete summoned pet + this.despawn(); + WorldGrid.RemoveWorldObject(this); + DbManager.removeFromCache(this); + + if (this.getObjectType() == GameObjectType.Mob) + if (this.getParentZone() != null) + this.getParentZone().zoneMobSet.remove(this); + + } else { //revert charmed pet + this.agentType = AIAgentType.MOBILE; + this.setCombatTarget(null); + } + //clear owner + + PlayerCharacter owner = this.getOwner(); + + //close pet window + + if (owner != null) { + Mob pet = owner.getPet(); + PetMsg pm = new PetMsg(5, null); + Dispatch dispatch = Dispatch.borrow(owner, pm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + if (pet != null && pet.getObjectUUID() == this.getObjectUUID()) + owner.setPet(null); + + if (this.getObjectType().equals(GameObjectType.Mob)) + this.setOwner(null); + } + } + } + +} \ No newline at end of file diff --git a/src/engine/objects/MobBase.java b/src/engine/objects/MobBase.java new file mode 100644 index 00000000..80adffb0 --- /dev/null +++ b/src/engine/objects/MobBase.java @@ -0,0 +1,304 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import ch.claude_martin.enumbitset.EnumBitSet; +import engine.Enum; +import engine.gameManager.DbManager; +import engine.gameManager.LootManager; +import engine.loot.BootySetEntry; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class MobBase extends AbstractGameObject { + + private final int loadID; + private final String firstName; + private final byte level; + private final float scale; + public int bootySet; + public Enum.MobBehaviourType fsm; + public EnumBitSet notEnemy; + public EnumBitSet enemy; + private float healthMax; + private int attackRating; + private int defenseRating; + private float damageMin; + private float damageMax; + private float hitBoxRadius; + private EnumBitSet flags; + private int mask; + private int goldMod; + private int seeInvis; + private int spawnTime = 0; + private int defense = 0; + private int atr = 0; + private float minDmg = 0; + private float maxDmg = 0; + private float attackRange; + private boolean isNecroPet = false; + private MobBaseStats mobBaseStats; + private float walk = 0; + private float run = 0; + private float walkCombat = 0; + private float runCombat = 0; + + /** + * ResultSet Constructor + */ + public MobBase(ResultSet rs) throws SQLException { + super(rs, rs.getInt("ID")); + + this.loadID = rs.getInt("loadID"); + + this.firstName = rs.getString("name"); + this.level = rs.getByte("level"); + + this.goldMod = rs.getInt("goldMod"); + this.spawnTime = rs.getInt("spawnTime"); + + this.healthMax = rs.getInt("health"); + this.damageMin = rs.getFloat("minDmg"); + this.damageMax = rs.getFloat("maxDmg"); + + this.attackRating = rs.getInt("atr"); + this.defenseRating = rs.getInt("defense"); + this.attackRange = rs.getFloat("attackRange"); + this.bootySet = rs.getInt("bootySet"); + + this.fsm = Enum.MobBehaviourType.valueOf(rs.getString("fsm")); + + this.flags = EnumBitSet.asEnumBitSet(rs.getLong("flags"), Enum.MobFlagType.class); + this.notEnemy = EnumBitSet.asEnumBitSet(rs.getLong("notEnemy"), Enum.MonsterType.class); + this.enemy = EnumBitSet.asEnumBitSet(rs.getLong("enemy"), Enum.MonsterType.class); + + this.seeInvis = rs.getInt("seeInvis"); + this.scale = rs.getFloat("scale"); + this.hitBoxRadius = 5f; + this.mask = 0; + + if (this.getObjectUUID() == 12021 || this.getObjectUUID() == 12022) + this.isNecroPet = true; + + if (Enum.MobFlagType.HUMANOID.elementOf(this.flags)) + this.mask += MBServerStatics.MASK_HUMANOID; + + if (Enum.MobFlagType.UNDEAD.elementOf(this.flags)) + this.mask += MBServerStatics.MASK_UNDEAD; + + if (Enum.MobFlagType.BEAST.elementOf(this.flags)) + this.mask += MBServerStatics.MASK_BEAST; + + if (Enum.MobFlagType.DRAGON.elementOf(this.flags)) + this.mask += MBServerStatics.MASK_DRAGON; + + if (Enum.MobFlagType.RAT.elementOf(this.flags)) + this.mask += MBServerStatics.MASK_RAT; + + this.mobBaseStats = DbManager.MobBaseQueries.LOAD_STATS(this.loadID); + DbManager.MobBaseQueries.LOAD_ALL_MOBBASE_SPEEDS(this); + + } + + public static HashMap loadEquipmentSet(int equipmentSetID) { + + ArrayList equipList; + HashMap equip = new HashMap<>(); + + if (equipmentSetID == 0) + return equip; + + equipList = LootManager._bootySetMap.get(equipmentSetID); + + if (equipList == null) + return equip; + + for (BootySetEntry equipmentSetEntry : equipList) { + + MobEquipment mobEquipment = new MobEquipment(equipmentSetEntry.itemBase, equipmentSetEntry.dropChance); + ItemBase itemBase = mobEquipment.getItemBase(); + + if (itemBase != null) { + if (itemBase.getType().equals(Enum.ItemType.WEAPON)) + if (mobEquipment.getSlot() == 1 && itemBase.getEquipFlag() == 2) + mobEquipment.setSlot(2); + + equip.put(mobEquipment.getSlot(), mobEquipment); + } + } + + return equip; + } + + public static MobBase getMobBase(int id) { + return DbManager.MobBaseQueries.GET_MOBBASE(id); + } + + public static int GetClassType(int mobbaseID) { + + switch (mobbaseID) { + case 17235: + case 17233: + case 17256: + case 17259: + case 17260: + case 17261: + return 2518; + case 17258: + case 17257: + case 17237: + case 17234: + return 2521; + default: + return 2518; + } + } + + public void updateSpeeds(float walk, float walkCombat, float run, float runCombat) { + this.walk = walk; + this.walkCombat = walkCombat; + this.run = run; + this.runCombat = runCombat; + + } + + /* + * Getters + */ + public String getFirstName() { + return this.firstName; + } + + public int getLoadID() { + return this.loadID; + } + + public int getLevel() { + return this.level; + } + + public float getHealthMax() { + return this.healthMax; + } + + public float getDamageMin() { + return this.damageMin; + } + + public float getDamageMax() { + return this.damageMax; + } + + public int getAttackRating() { + return this.attackRating; + } + + public int getDefenseRating() { + return this.defenseRating; + } + + public EnumBitSet getFlags() { + return this.flags; + } + + public float getScale() { + return this.scale; + } + + public int getTypeMasks() { + return this.mask; + } + + public int getSeeInvis() { + return this.seeInvis; + } + + public int getSpawnTime() { + return this.spawnTime; + } + + @Override + public void updateDatabase() { + // TODO Create update logic. + } + + public float getHitBoxRadius() { + if (this.hitBoxRadius < 0f) { + return 0f; + } else { + return this.hitBoxRadius; + } + } + + public MobBaseStats getMobBaseStats() { + return mobBaseStats; + } + + public float getMaxDmg() { + return maxDmg; + } + + public float getMinDmg() { + return minDmg; + } + + public int getAtr() { + return atr; + } + + public void setAtr(int atr) { + this.atr = atr; + } + + public int getDefense() { + return defense; + } + + public void setDefense(int defense) { + this.defense = defense; + } + + public float getAttackRange() { + return attackRange; + } + + public boolean isNecroPet() { + return isNecroPet; + } + + public float getWalk() { + return walk; + } + + public void setWalk(float walk) { + this.walk = walk; + } + + public float getRun() { + return run; + } + + public void setRun(float run) { + this.run = run; + } + + public float getWalkCombat() { + return walkCombat; + } + + public float getRunCombat() { + return runCombat; + } + +} diff --git a/src/engine/objects/MobBaseEffects.java b/src/engine/objects/MobBaseEffects.java new file mode 100644 index 00000000..4f938ef2 --- /dev/null +++ b/src/engine/objects/MobBaseEffects.java @@ -0,0 +1,62 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class MobBaseEffects { + + private int mobBaseID; + private int token; + private int rank; + private int reqLvl; + + + /** + * ResultSet Constructor + */ + public MobBaseEffects(ResultSet rs) throws SQLException { + this.token = rs.getInt("token"); + this.rank = rs.getInt("rank"); + this.reqLvl = rs.getInt("reqLvl"); + } + + + /** + * @return the mobBaseID + */ + public int getMobBaseID() { + return mobBaseID; + } + + + public void setMobBaseID(int mobBaseID) { + this.mobBaseID = mobBaseID; + } + + + public int getToken() { + return token; + } + + + public int getRank() { + return rank; + } + + + public int getReqLvl() { + return reqLvl; + } + + +} diff --git a/src/engine/objects/MobBaseStats.java b/src/engine/objects/MobBaseStats.java new file mode 100644 index 00000000..38a09379 --- /dev/null +++ b/src/engine/objects/MobBaseStats.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MobBaseStats { + + public static MobBaseStats mbs = null; + private final int baseStr; + private final int baseInt; + private final int baseCon; + private final int baseSpi; + private final int baseDex; + + + /** + * ResultSet Constructor + */ + public MobBaseStats(ResultSet rs) throws SQLException { + this.baseStr = rs.getInt("Strength"); + this.baseInt = rs.getInt("Intelligence"); + this.baseCon = rs.getInt("Constitution"); + this.baseSpi = rs.getInt("Spirit"); + this.baseDex = rs.getInt("Dexterity"); + } + + /** + * Generic Constructor + */ + + public MobBaseStats() { + this.baseStr = 0; + this.baseInt = 0; + this.baseCon = 0; + this.baseSpi = 0; + this.baseDex = 0; + } + + public static MobBaseStats GetGenericStats() { + if (mbs != null) + return mbs; + mbs = new MobBaseStats(); + return mbs; + } + + public int getBaseStr() { + return baseStr; + } + + public int getBaseInt() { + return baseInt; + } + + public int getBaseCon() { + return baseCon; + } + + public int getBaseSpi() { + return baseSpi; + } + + public int getBaseDex() { + return baseDex; + } + + +} diff --git a/src/engine/objects/MobEquipment.java b/src/engine/objects/MobEquipment.java new file mode 100644 index 00000000..d15b6274 --- /dev/null +++ b/src/engine/objects/MobEquipment.java @@ -0,0 +1,361 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.exception.SerializationException; +import engine.gameManager.PowersManager; +import engine.net.ByteBufferWriter; +import engine.powers.EffectsBase; +import engine.powers.poweractions.AbstractPowerAction; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicInteger; + +public class MobEquipment extends AbstractGameObject { + + private static AtomicInteger equipCounter = new AtomicInteger(0); + private final ItemBase itemBase; + private int slot; + private int parentID; + + //effects + private boolean enchanted; + private boolean isID = false; + private AbstractPowerAction prefix; + private AbstractPowerAction suffix; + private int pValue; + private int sValue; + private int magicValue; + + private float dropChance = 0; + + /** + * No Id Constructor + */ + public MobEquipment(ItemBase itemBase, int slot, int parentID) { + super(MobEquipment.getNewID()); + this.itemBase = itemBase; + this.slot = slot; + this.parentID = parentID; + this.enchanted = false; + this.prefix = null; + this.suffix = null; + this.pValue = 0; + this.sValue = 0; + setMagicValue(); + } + + public MobEquipment(ItemBase itemBase, int slot, int parentID, String pIDString, String sIDString, int pValue, int sValue) { + super(MobEquipment.getNewID()); + this.itemBase = itemBase; + this.slot = slot; + this.parentID = parentID; + + //add effects + this.prefix = PowersManager.getPowerActionByIDString(pIDString); + this.suffix = PowersManager.getPowerActionByIDString(sIDString); + + this.pValue = pValue; + this.sValue = sValue; + this.enchanted = this.prefix == null || this.suffix == null; + setMagicValue(); + } + + /** + * ResultSet Constructor + */ + public MobEquipment(ResultSet rs) throws SQLException { + super(MobEquipment.getNewID()); + int itemBaseID = rs.getInt("ItemID"); + this.itemBase = ItemBase.getItemBase(itemBaseID); + this.slot = rs.getInt("slot"); + this.parentID = rs.getInt("mobID"); + setMagicValue(); + } + + + public MobEquipment(int itemBaseID, float dropChance) { + super(MobEquipment.getNewID()); + this.itemBase = ItemBase.getItemBase(itemBaseID); + + if (this.itemBase != null) + this.slot = this.itemBase.getValidSlot(); + else { + Logger.error("Failed to find Itembase for ID : " + itemBaseID); + this.slot = 0; + } + + this.dropChance = dropChance; + + this.parentID = 0; + setMagicValue(); + } + + public static int getNewID() { + return MobEquipment.equipCounter.incrementAndGet(); + } + + public static void serializeForVendor(MobEquipment mobEquipment, ByteBufferWriter writer, float percent) throws SerializationException { + _serializeForClientMsg(mobEquipment, writer, false); + int baseValue = mobEquipment.itemBase.getBaseValue() + mobEquipment.itemBase.getMagicValue(); + writer.putInt(mobEquipment.magicValue); + writer.putInt(mobEquipment.magicValue); + } + + public static void serializeForClientMsg(MobEquipment mobEquipment, ByteBufferWriter writer) throws SerializationException { + _serializeForClientMsg(mobEquipment, writer, true); + } + + public static void _serializeForClientMsg(MobEquipment mobEquipment, ByteBufferWriter writer, boolean useSlot) throws SerializationException { + + if (useSlot) + writer.putInt(mobEquipment.slot); + writer.putInt(0); // Pad + writer.putInt(mobEquipment.itemBase.getUUID()); + writer.putInt(mobEquipment.getObjectType().ordinal()); + writer.putInt(mobEquipment.getObjectUUID()); + + // Unknown statics + for (int i = 0; i < 3; i++) { + writer.putInt(0); // Pad + } + for (int i = 0; i < 4; i++) { + writer.putInt(0x3F800000); // Static + } + for (int i = 0; i < 5; i++) { + writer.putInt(0); // Pad + } + for (int i = 0; i < 2; i++) { + writer.putInt(0xFFFFFFFF); // Static + } + + writer.putInt(0); + + writer.put((byte) 1); // End Datablock byte + writer.putInt(0); // Unknown. pad? + writer.put((byte) 1); // End Datablock byte + + writer.putFloat(mobEquipment.itemBase.getDurability()); + writer.putFloat(mobEquipment.itemBase.getDurability()); + + writer.put((byte) 1); // End Datablock byte + + writer.putInt(0); // Pad + writer.putInt(0); // Pad + + writer.putInt(mobEquipment.itemBase.getBaseValue()); + writer.putInt(mobEquipment.magicValue); + + serializeEffects(mobEquipment, writer); + + writer.putInt(0x00000000); + + //name color, think mobEquipment is where mobEquipment goes + if (mobEquipment.enchanted) + if (mobEquipment.isID) + writer.putInt(36); + else + writer.putInt(40); + else + writer.putInt(4); + + writer.putInt(0); + writer.putInt(0); // Pad + writer.putInt(1); + writer.putShort((short) 0); + writer.put((byte) 0); + } + + public static void serializeEffects(MobEquipment mobEquipment, ByteBufferWriter writer) { + + //skip sending effects if not IDed + if (!mobEquipment.isID) { + writer.putInt(0); + return; + } + + //handle effect count + int cnt = 0; + EffectsBase pre = null; + EffectsBase suf = null; + if (mobEquipment.prefix != null) { + pre = PowersManager.getEffectByIDString(mobEquipment.prefix.getIDString()); + if (pre != null) + cnt++; + } + if (mobEquipment.suffix != null) { + suf = PowersManager.getEffectByIDString(mobEquipment.suffix.getIDString()); + if (suf != null) + cnt++; + } + + writer.putInt(cnt); + + //serialize prefix + if (pre != null) + serializeEffect(mobEquipment, writer, pre, mobEquipment.pValue); + + //serialize suffix + if (suf != null) + serializeEffect(mobEquipment, writer, suf, mobEquipment.sValue); + } + + public static void serializeEffect(MobEquipment mobEquipment, ByteBufferWriter writer, EffectsBase eb, int rank) { + String name; + if (eb.isPrefix()) { + if (mobEquipment.itemBase == null) + name = eb.getName(); + else + name = eb.getName() + ' ' + mobEquipment.itemBase.getName(); + } else if (eb.isSuffix()) { + if (mobEquipment.itemBase == null) + name = eb.getName(); + else + name = mobEquipment.itemBase.getName() + ' ' + eb.getName(); + } else { + if (mobEquipment.itemBase == null) + name = ""; + else + name = mobEquipment.itemBase.getName(); + } + + writer.putInt(eb.getToken()); + writer.putInt(rank); + writer.putInt(1); + writer.put((byte) 1); + writer.putInt(mobEquipment.getObjectType().ordinal()); + writer.putInt(mobEquipment.getObjectUUID()); + writer.putString(name); + writer.putFloat(-1000f); + } + + public ItemBase getItemBase() { + return itemBase; + } + + public int getSlot() { + return this.slot; + } + + public void setSlot(int value) { + this.slot = value; + } + + public final void setMagicValue() { + float value = 1; + if (itemBase != null) + value = itemBase.getBaseValue(); + if (this.prefix != null) { + if (this.prefix.getEffectsBase() != null) + value += this.prefix.getEffectsBase().getValue(); + if (this.prefix.getEffectsBase2() != null) + value += this.prefix.getEffectsBase2().getValue(); + } + if (this.suffix != null) { + if (this.suffix.getEffectsBase() != null) + value += this.suffix.getEffectsBase().getValue(); + if (this.suffix.getEffectsBase2() != null) + value += this.suffix.getEffectsBase2().getValue(); + } + + if (itemBase != null) + + for (Integer token : itemBase.getBakedInStats().keySet()) { + + EffectsBase effect = PowersManager.getEffectByToken(token); + + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(effect.getIDString()); + if (apa.getEffectsBase() != null) + if (apa.getEffectsBase().getValue() > 0) { + //System.out.println(apa.getEffectsBase().getValue()); + value += apa.getEffectsBase().getValue(); + } + + + if (apa.getEffectsBase2() != null) + value += apa.getEffectsBase2().getValue(); + } + + this.magicValue = (int) value; + } + + public int getMagicValue() { + + if (!this.isID) { + return itemBase.getBaseValue(); + } + return this.magicValue; + } + + public void setPrefix(String pIDString, int pValue) { + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(pIDString); + if (apa != null) { + this.prefix = apa; + this.pValue = pValue; + } else + this.prefix = null; + + this.enchanted = this.prefix != null || this.suffix != null; + + setMagicValue(); + } + + public void setSuffix(String sIDString, int sValue) { + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(sIDString); + if (apa != null) { + this.suffix = apa; + this.sValue = sValue; + } else + this.suffix = null; + + this.enchanted = this.prefix != null || this.suffix != null; + + setMagicValue(); + } + + public void setIsID(boolean value) { + this.isID = value; + } + + public boolean isID() { + return this.isID; + } + + public void transferEnchants(Item item) { + if (this.prefix != null) { + String IDString = this.prefix.getIDString(); + item.addPermanentEnchantment(IDString, this.pValue); + } + if (this.suffix != null) { + String IDString = this.suffix.getIDString(); + item.addPermanentEnchantment(IDString, this.sValue); + } + if (this.isID) + item.setIsID(true); + } + + /* + * Database + */ + @Override + public void updateDatabase() { + } + + + public float getDropChance() { + return dropChance; + } + + public void setDropChance(float dropChance) { + this.dropChance = dropChance; + } +} diff --git a/src/engine/objects/MobLoot.java b/src/engine/objects/MobLoot.java new file mode 100644 index 00000000..235eccd1 --- /dev/null +++ b/src/engine/objects/MobLoot.java @@ -0,0 +1,396 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.ItemType; +import engine.Enum.OwnerType; +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.powers.poweractions.AbstractPowerAction; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * An immutable, non-persistant implementation of Item + * + * @author Burfo + */ +public final class MobLoot extends Item { + + private static final AtomicInteger LastUsedId = new AtomicInteger(0); + + private boolean isDeleted = false; + private boolean noSteal; + private String prefix = ""; + private String suffix = ""; + + /** + * Create a new MobLoot. + * Do not use this to create Gold. + * + * @param mob Mob that owns this item + * @param ib ItemBase + */ + public MobLoot(AbstractCharacter mob, ItemBase ib, boolean noSteal) { + this(mob, ib, 0, noSteal); + } + + /** + * Create a new MobLoot item to hold Gold for the Mob. + * + * @param mob Mob that owns this item + * @param qtyOfGold Quantity of gold + */ + public MobLoot(AbstractCharacter mob, int qtyOfGold) { + this(mob, ItemBase.getGoldItemBase(), qtyOfGold, false); + } + + /** + * Create a new MobLoot. + * Primarily used for stackable items that have a quantity. + * + * @param mob Mob that owns this item + * @param ib ItemBase + * @param quantity Quantity of the item + */ + public MobLoot(AbstractCharacter mob, ItemBase ib, int quantity, boolean noSteal) { + super(ib, mob.getObjectUUID(), + OwnerType.Mob, (byte) 0, (byte) 0, (short) 0, + (short) 0, true, false, false, false, true, + false, (byte) 0, new ArrayList<>(), generateId()); + + if (quantity == 0 && ib.getType() == ItemType.RESOURCE) + quantity = 1; + + + if (quantity > 0) + this.setNumOfItems(quantity); + + this.noSteal = noSteal; + this.setIsID(this.getItemBase().isAutoID()); + + // Class is 'final'; passing 'this' should be okay at the end of the constructor + + DbManager.addToCache(this); + } + + /** + * Get the MobLoot object from its Id number + * + * @param id Id Number + * @return MobLoot object + */ + public static MobLoot getFromCache(int id) { + return (MobLoot) DbManager.getFromCache(Enum.GameObjectType.MobLoot, id); + } + + /** + * Get the next available Id number. + * + * @return Id number + */ + private static int generateId() { + int id = LastUsedId.decrementAndGet(); + + //TODO Add a way to reclaim disposed IDs if this becomes a problem + if (id == (-10000)) + Logger.warn("Only 10,000 Id numbers remain useable. Server restart suggested."); + else if (id < Integer.MIN_VALUE + 1000) + Logger.warn("Only " + (Integer.MIN_VALUE + id) + + " Id numbers remain useable! Server restart suggested."); + else if (id == Integer.MIN_VALUE) + throw new UnsupportedOperationException("MobLoot has no remaining Id numbers! Restart server immediately!"); + else if ((id % 10000) == 0) + Logger.info(id + " of " + Integer.MIN_VALUE + " Id numbers consumed."); + + return id; + } + + /** + * Converts this MotLoot to a persistable Item. Used when a MotLoot is + * looted + * from a Mob to a Player. Do not call for a Gold item. + * + * @return An orphaned Item, ready to be moved to the Player's inventory. + */ + public synchronized Item promoteToItem(PlayerCharacter looter) { + + if (looter == null) + return null; + + if (isDeleted) + return null; + + if (this.getItemBase().getType().equals(ItemType.GOLD)) + return null; + + + Item item = (Item) this; + + item.setOwner(looter); + //item.setIsID(false); + + item.containerType = Enum.ItemContainerType.INVENTORY; + item.setValue(0); + item.setName(this.getCustomName()); + item.setIsID(this.isID()); + + if (this.getNumOfItems() > 1) + item.setNumOfItems(this.getNumOfItems()); + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + } catch (Exception e) { + Logger.error("e"); + return null; + } + + // for (String effectName : this.effectNames) + // item.addPermanentEnchantment(effectName, 0); + //transfer enchantments to item + if (this.prefix.length() != 0) + item.addPermanentEnchantment(this.prefix, 0); + if (this.suffix.length() != 0) + item.addPermanentEnchantment(this.suffix, 0); + + this.junk(); + return item; + } + + public synchronized Item promoteToItemForNPC(NPC looter) { + + if (looter == null) + return null; + + if (isDeleted) + return null; + + if (this.getItemBase().getType().equals(ItemType.GOLD)) + return null; + + Item item = this; + item.setOwner(looter); + item.containerType = Enum.ItemContainerType.INVENTORY; + item.setIsID(true); + + if (this.getNumOfItems() > 1) + item.setNumOfItems(this.getNumOfItems()); + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + } catch (Exception e) { + Logger.error(e); + return null; + } + item.containerType = Enum.ItemContainerType.INVENTORY; + + // for (String effectName : this.effectNames) + // item.addPermanentEnchantment(effectName, 0); + //transfer enchantments to item + try { + for (String enchant : this.getEffectNames()) { + item.addPermanentEnchantment(enchant, 0); + } + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + DbManager.NPCQueries.REMOVE_FROM_PRODUCTION_LIST(this.getObjectUUID(), looter.getObjectUUID()); + looter.removeItemFromForge(this); + this.junk(); + return item; + } + + public synchronized void recycle(NPC vendor) { + + //remove from production list for npc in db + + DbManager.NPCQueries.REMOVE_FROM_PRODUCTION_LIST(this.getObjectUUID(), vendor.getObjectUUID()); + this.removeFromCache(); + isDeleted = true; + } + + /** + * Junks the item and marks it as deleted + */ + @Override + protected synchronized void junk() { + this.removeFromCache(); + isDeleted = true; + } + + /** + * Determines if this object has been marked as deleted. + * + * @return True if deleted. + */ + public boolean isDeleted() { + return this.isDeleted; + } + + public boolean noSteal() { + return this.noSteal; + } + + public void addPermanentEnchantment(String enchantID, int rank, int value, boolean prefix) { + AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID); + if (apa == null) + return; + apa.applyEffectForItem(this, rank); + + //limit to 2 effects + // if (this.effectNames.size() < 2) + // this.effectNames.add(enchantID); + if (prefix) + this.prefix = enchantID; + else + this.suffix = enchantID; + + this.getEffectNames().add(enchantID); + } + + /* ***** + * All of the following methods are overridden from + * the superclass and intentionally not implemented. + * ***** + */ + + /** + * Not implemented + */ + @Override + @Deprecated + public void setOwnerID(int id) { + } + + /** + * Not implemented + */ + @Override + @Deprecated + public synchronized void decrementChargesRemaining() { + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean equipItem(NPC npc, byte slot) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean equipItem(PlayerCharacter pc, byte slot) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean moveItemToBank(NPC npc) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean moveItemToBank(PlayerCharacter pc) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean moveItemToInventory(Corpse corpse) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean moveItemToInventory(NPC npc) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean moveItemToInventory(PlayerCharacter pc) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected boolean moveItemToVault(Account a) { + return false; + } + + /** + * Not implemented + */ + @Override + @Deprecated + public void setLastOwner(AbstractWorldObject value) { + } + + /** + * Not implemented + */ + @Override + @Deprecated + public void updateDatabase() { + } + + /** + * Not implemented + */ + @Override + @Deprecated + protected void validateItemContainer() { + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + +} diff --git a/src/engine/objects/NPC.java b/src/engine/objects/NPC.java new file mode 100644 index 00000000..1e6d2f83 --- /dev/null +++ b/src/engine/objects/NPC.java @@ -0,0 +1,1350 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.*; +import engine.InterestManagement.WorldGrid; +import engine.exception.SerializationException; +import engine.gameManager.*; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.UpgradeNPCJob; +import engine.math.Bounds; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.net.ByteBufferWriter; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.ErrorPopupMsg; +import engine.net.client.msg.ItemProductionMsg; +import engine.net.client.msg.PetMsg; +import engine.net.client.msg.PlaceAssetMsg; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup; +import static engine.objects.MobBase.loadEquipmentSet; +import static engine.util.StringUtils.wordCount; + +public class NPC extends AbstractCharacter { + + public static int SVR_CLOSE_WINDOW = 4; + public static HashMap> _pirateNames = new HashMap<>(); + // Used for thread safety + public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ArrayList rolling = new ArrayList<>(); + private final ConcurrentHashMap siegeMinionMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public ReentrantReadWriteLock minionLock = new ReentrantReadWriteLock(); + public ArrayList forgedItems = new ArrayList<>(); + public HashMap equip = null; + public int runeSetID = 0; + public int extraRune2 = 0; + protected int loadID; + protected MobBase mobBase; + protected String name; + protected int dbID; + protected int currentID; + //used by static npcs + protected Zone parentZone; + protected float statLat; + protected float statLon; + protected float statAlt; + public float sellPercent; //also train percent + public float buyPercent; + protected int vendorID; + protected ArrayList modTypeTable; + protected ArrayList modSuffixTable; + protected ArrayList itemModTable; + protected int symbol; + // Variables NOT to be stored in db + protected boolean isStatic = false; + private DateTime upgradeDateTime = null; + private HashSet canRoll = null; + public int parentZoneUUID; + public int equipmentSetID = 0; + private int repairCost = 5; + + // New NPC constructor. Fill in the blanks and then call + // PERSIST. + + public NPC() { + + this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET; + this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET; + } + + /** + * ResultSet Constructor + */ + public NPC(ResultSet rs) throws SQLException { + + super(rs); + + java.util.Date sqlDateTime; + + try { + this.dbID = rs.getInt(1); + this.currentID = this.dbID; + this.setObjectTypeMask(MBServerStatics.MASK_NPC); + this.contractUUID = rs.getInt("npc_contractID"); + this.parentZoneUUID = rs.getInt("parent"); + this.gridObjectType = GridObjectType.STATIC; + this.equipmentSetID = rs.getInt("equipmentSet"); + this.runeSetID = rs.getInt("runeSet"); + + this.loadID = rs.getInt("npc_raceID"); + + this.level = rs.getByte("npc_level"); + + buildingUUID = rs.getInt("npc_buildingID"); + + // Most objects from the cache have a default buy + // percentage of 100% which was a dupe source due + // to the way MB calculated item values. + + // this.buyPercent = rs.getFloat("npc_buyPercent"); + + this.buyPercent = .33f; + this.sellPercent = 1; + + this.setRot(new Vector3f(0, rs.getFloat("npc_rotation"), 0)); + + this.statLat = rs.getFloat("npc_spawnX"); + this.statAlt = rs.getFloat("npc_spawnY"); + this.statLon = rs.getFloat("npc_spawnZ"); + + this.guildUUID = rs.getInt("npc_guildID"); + + // Set upgrade date JodaTime DateTime object + // if one exists in the database. + + sqlDateTime = rs.getTimestamp("upgradeDate"); + + if (sqlDateTime != null) + upgradeDateTime = new DateTime(sqlDateTime); + else + upgradeDateTime = null; + + // Submit upgrade job if NPC is currently set to rank. + + if (this.upgradeDateTime != null) + submitUpgradeJob(); + + this.name = rs.getString("npc_name"); + + } catch (Exception e) { + Logger.error("NPC: " + this.dbID + " :" + e); + e.printStackTrace(); + } + + } + + public static boolean ISWallArcher(Contract contract) { + + if (contract == null) + return false; + + //838, 950, 1051, 1181, 1251, 1351, 1451, 1501, 1526, 1551, 980101, + + return contract.getAllowedBuildings().contains(BuildingGroup.WALLCORNER) || + contract.getAllowedBuildings().contains(BuildingGroup.WALLSTRAIGHTTOWER); + } + + //This method restarts an upgrade timer when a building is loaded from the database. + // Submit upgrade job for this building based upon it's current upgradeDateTime + + public static NPC getFromCache(int id) { + return (NPC) DbManager.getFromCache(GameObjectType.NPC, id); + } + + public static boolean UpdateName(NPC npc, String value) { + + if (!DbManager.NPCQueries.UPDATE_NAME(npc, value)) + return false; + + npc.name = value; + return true; + + } + + public static void serializeNpcForClientMsgOtherPlayer(NPC npc, ByteBufferWriter writer, boolean hideAsciiLastName) + throws SerializationException { + serializeForClientMsgOtherPlayer(npc, writer); + } + + public static void serializeForClientMsgOtherPlayer(NPC npc, ByteBufferWriter writer) + throws SerializationException { + + writer.putInt(0); + writer.putInt(0); + + //num Runes + int cnt = 3; + boolean isVamp = false, isHealer = false, isArcher = false, isTrainer = false; + int contractID = 0, classID = 0; + int extraRune = 0; + + if (npc.contract != null) { + contractID = npc.contract.getContractID(); + classID = npc.contract.getClassID(); + extraRune = npc.contract.getExtraRune(); + + if (extraRune == contractID) + extraRune = 0; + + } + + if ((contractID > 252642 && contractID < 252647) || contractID == 252652) { + isVamp = true; + cnt++; + } + + if (contractID == 252582 || contractID == 252579 || contractID == 252581 + || contractID == 252584 || contractID == 252597 || contractID == 252598 + || contractID == 252628 || extraRune == 252582 || extraRune == 252579 + || extraRune == 252581 || extraRune == 252584 || extraRune == 252597 + || extraRune == 252598 || extraRune == 252628) { + isHealer = true; + cnt++; + } + + if (contractID == 252570) { + isArcher = true; + cnt++; + } + + if (classID != 0) + cnt++; + + if (extraRune != 0 && extraRune != contractID) + cnt++; + + writer.putInt(cnt); + + //Race + writer.putInt(1); + writer.putInt(0); + + if (npc.mobBase != null) + writer.putInt(npc.mobBase.getLoadID()); + else + writer.putInt(2011); + + writer.putInt(GameObjectType.NPCRaceRune.ordinal()); + writer.putInt(npc.currentID); + + //Class/Trainer/Whatever + writer.putInt(5); + writer.putInt(0); + + if (npc.contract != null) + writer.putInt(contractID); + else + writer.putInt(2500); + + writer.putInt(GameObjectType.NPCClassRune.ordinal()); + writer.putInt(npc.currentID); + + //vampire trainer + cnt = 0; + + if (extraRune != 0) + cnt = serializeExtraRune(npc, extraRune, cnt, writer); + if (isVamp) + cnt = serializeExtraRune(npc, 252647, cnt, writer); + + //Healer trainer + if (isHealer) + cnt = serializeExtraRune(npc, 252592, cnt, writer); + + if (classID != 0) { + writer.putInt(4); + writer.putInt(0); + writer.putInt(classID); + writer.putInt(GameObjectType.NPCExtraRune.ordinal()); + writer.putInt(npc.currentID); + } + + //Scout trainer + if (isArcher) + cnt = serializeExtraRune(npc, 252654, cnt, writer); + + //Shopkeeper + writer.putInt(5); + writer.putInt(0); + writer.putInt(0x3DACC); + writer.putInt(GameObjectType.NPCShopkeeperRune.ordinal()); + writer.putInt(npc.currentID); + + //Send Stats + writer.putInt(5); + writer.putInt(0x8AC3C0E6); //Str + writer.putInt(0); + writer.putInt(0xACB82E33); //Dex + writer.putInt(0); + writer.putInt(0xB15DC77E); //Con + writer.putInt(0); + writer.putInt(0xE07B3336); //Int + writer.putInt(0); + writer.putInt(0xFF665EC3); //Spi + writer.putInt(0); + + writer.putString(npc.name); + writer.putString(""); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.put((byte) 0); + writer.putInt(npc.getObjectType().ordinal()); + writer.putInt(npc.currentID); + + writer.putFloat(1.0f); + writer.putFloat(1.0f); + writer.putFloat(1.0f); + + if (npc.region != null) + writer.putVector3f(ZoneManager.convertWorldToLocal(npc.building, npc.getLoc())); + else + writer.putVector3f(npc.getLoc()); + + //Rotation + float radians = (float) Math.acos(npc.getRot().y) * 2; + + if (npc.building != null) + if (npc.building.getBounds() != null && npc.building.getBounds().getQuaternion() != null) + radians += (npc.building.getBounds().getQuaternion()).angleY; + + writer.putFloat(radians); + + //Running Speed + writer.putInt(0); + + // get a copy of the equipped items. + + if (npc.equip != null) { + writer.putInt(npc.equip.size()); + + for (MobEquipment me : npc.equip.values()) + MobEquipment.serializeForClientMsg(me, writer); + } else + writer.putInt(0); + + writer.putInt((npc.level / 10)); + writer.putInt(npc.level); + writer.putInt(npc.getIsSittingAsInt()); //Standing + writer.putInt(npc.getIsWalkingAsInt()); //Walking + writer.putInt(npc.getIsCombatAsInt()); //Combat + writer.putInt(2); //Unknown + writer.putInt(1); //Unknown - Headlights? + writer.putInt(0); + + if (npc.building != null && npc.region != null) { + writer.putInt(npc.building.getObjectType().ordinal()); + writer.putInt(npc.building.getObjectUUID()); + } else { + writer.putInt(0); //<-Building Object Type + writer.putInt(0); //<-Building Object ID + } + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + + //npc dialog menus from contracts + + if (npc.contract != null) { + ArrayList npcMenuOptions = npc.contract.getNPCMenuOptions(); + writer.putInt(npcMenuOptions.size()); + for (Integer val : npcMenuOptions) { + writer.putInt(val); + } + + } else + writer.putInt(0); + + writer.put((byte) 1); + + if (npc.building != null) { + writer.putInt(GameObjectType.StrongBox.ordinal()); + writer.putInt(npc.currentID); + writer.putInt(GameObjectType.StrongBox.ordinal()); + writer.putInt(npc.building.getObjectUUID()); + } else { + writer.putLong(0); + writer.putLong(0); + } + + if (npc.contract != null) + writer.putInt(npc.contract.getIconID()); + else + writer.putInt(0); //npc icon ID + + writer.putInt(0); + writer.putShort((short) 0); + + if (npc.contract != null && npc.contract.isTrainer()) { + writer.putInt(classID); + } else { + writer.putInt(0); + } + + if (npc.contract != null && npc.contract.isTrainer()) + writer.putInt(classID); + else + writer.putInt(0); + + writer.putInt(0); + writer.putInt(0); + + writer.putFloat(4); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.put((byte) 0); + + //Pull guild info from building if linked to one + + Guild.serializeForClientMsg(npc.guild, writer, null, true); + + writer.putInt(1); + writer.putInt(0x8A2E); + writer.putInt(0); + writer.putInt(0); + + //TODO Guard + writer.put((byte) 0); //Is guard.. + + writer.putFloat(1500f); //npc.healthMax + writer.putFloat(1500f); //npc.health + + //TODO Peace Zone + writer.put((byte) 1); //0=show tags, 1=don't + writer.putInt(0); + writer.put((byte) 0); + } + + private static int serializeExtraRune(NPC npc, int runeID, int cnt, ByteBufferWriter writer) { + + writer.putInt(5); + writer.putInt(0); + writer.putInt(runeID); + + if (cnt == 0) + writer.putInt(GameObjectType.NPCClassRuneTwo.ordinal()); + else + writer.putInt(GameObjectType.NPCClassRuneThree.ordinal()); + + writer.putInt(npc.currentID); + return cnt + 1; + } + + public static NPC createNPC(String name, int contractID, Vector3fImmutable spawn, Guild guild, Zone parent, short level, Building building) { + + NPC newNPC = new NPC(); + + newNPC.name = name; + newNPC.contractUUID = contractID; + + if (building == null) + newNPC.bindLoc = spawn; + else + newNPC.bindLoc = Vector3fImmutable.ZERO; + + newNPC.parentZoneUUID = parent.getObjectUUID(); + newNPC.guildUUID = guild.getObjectUUID(); + + if (building == null) + newNPC.buildingUUID = 0; + else + newNPC.buildingUUID = building.getObjectUUID(); + + newNPC.level = level; + + newNPC.buyPercent = .33f; + newNPC.sellPercent = 1; + + NPC npc; + + try { + npc = DbManager.NPCQueries.PERSIST(newNPC); + npc.setObjectTypeMask(MBServerStatics.MASK_NPC); + } catch (Exception e) { + Logger.error(e); + npc = null; + } + + return npc; + } + + public static NPC getNPC(int id) { + + if (id == 0) + return null; + + NPC npc = (NPC) DbManager.getFromCache(GameObjectType.NPC, id); + + if (npc != null) + return npc; + + return DbManager.NPCQueries.GET_NPC(id); + } + + /* + * Getters + */ + + public static boolean ISGuardCaptain(int contractID) { + return MinionType.ContractToMinionMap.containsKey(contractID); + } + + public static boolean UpdateEquipSetID(NPC npc, int equipSetID) { + + if (!LootManager._bootySetMap.containsKey(equipSetID)) + return false; + + if (!DbManager.NPCQueries.UPDATE_EQUIPSET(npc, equipSetID)) + return false; + + npc.equipmentSetID = equipSetID; + + return true; + } + + public static boolean UpdateRaceID(NPC npc, int raceID) { + + if (!DbManager.NPCQueries.UPDATE_MOBBASE(npc, raceID)) + return false; + + npc.loadID = raceID; + npc.mobBase = MobBase.getMobBase(npc.loadID); + return true; + } + + public static NPCProfits GetNPCProfits(NPC npc) { + return NPCProfits.ProfitCache.get(npc.currentID); + } + + public final void submitUpgradeJob() { + + JobContainer jc; + + if (this.getUpgradeDateTime() == null) { + Logger.error("Attempt to submit upgrade job for non-ranking NPC"); + return; + } + + // Submit upgrade job for future date or current instant + + if (this.getUpgradeDateTime().isAfter(DateTime.now())) + jc = JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), + this.getUpgradeDateTime().getMillis()); + else + JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), 0); + + } + + public void setRank(int newRank) { + + DbManager.NPCQueries.SET_PROPERTY(this, "npc_level", newRank); + this.level = (short) newRank; + } + + public int getDBID() { + return this.dbID; + } + + @Override + public int getObjectUUID() { + return currentID; + } + + public MobBase getMobBase() { + return this.mobBase; + } + + public Contract getContract() { + return this.contract; + } + + public int getContractID() { + + if (this.contract != null) + return this.contract.getObjectUUID(); + + return 0; + } + + public boolean isStatic() { + return this.isStatic; + } + + public Building getBuilding() { + return this.building; + } + + public void setBuilding(Building value) { + this.building = value; + } + + @Override + public String getFirstName() { + return this.name; + } + + @Override + public String getName() { + return this.name; + } + + public void setName(String value) { + this.name = value; + } + + @Override + public String getLastName() { + return ""; + } + + @Override + public Vector3fImmutable getBindLoc() { + return this.bindLoc; + } + + @Override + public int getGuildUUID() { + + if (this.guild == null) + return 0; + + return this.guild.getObjectUUID(); + } + + public void removeMinions() { + + for (Mob toRemove : this.siegeMinionMap.keySet()) { + + try { + toRemove.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + if (toRemove.getParentZone() != null) + toRemove.getParentZone().zoneMobSet.remove(toRemove); + + WorldGrid.RemoveWorldObject(toRemove); + DbManager.removeFromCache(toRemove); + + PlayerCharacter petOwner = toRemove.getOwner(); + + if (petOwner != null) { + + petOwner.setPet(null); + toRemove.setOwner(null); + + PetMsg petMsg = new PetMsg(5, null); + Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + } + } + } + + @Override + public float getSpeed() { + + if (this.isWalk()) + return MBServerStatics.WALKSPEED; + else + return MBServerStatics.RUNSPEED; + } + + @Override + public float getPassiveChance(String type, int AttackerLevel, boolean fromCombat) { + //TODO add this later for dodge + return 0f; + } + + /** + * @ Kill this Character + */ + @Override + public void killCharacter(AbstractCharacter attacker) { + //TODO Handle Death + killCleanup(); + + //TODO Send death message if needed + } + + @Override + public void killCharacter(String reason) { + //TODO Handle Death + killCleanup(); + + //Orphan inventory so it can be looted + //if (!this.inSafeZone) + if (this.charItemManager != null) + this.charItemManager.orphanInventory(); + + //TODO Send death message if needed + //Question? How would a mob die to water? + } + + private void killCleanup() { + //TODO handle cleanup from death here + + //set so character won't load + this.load = false; + + //Create Corpse and add to world + //Corpse.makeCorpse(this); + //TODO damage equipped items + //TODO cleanup any timers + } + + public Zone getParentZone() { + return this.parentZone; + } + + public int getParentZoneUUID() { + + if (this.parentZone != null) + return this.parentZone.getObjectUUID(); + + return 0; + } + + @Override + public Vector3fImmutable getLoc() { + + return super.getLoc(); + } + + public float getSellPercent() { + return this.sellPercent; + } + + public float getBuyPercent() { + return this.buyPercent; + } + + public float getBuyPercent(PlayerCharacter player) { + if (NPC.GetNPCProfits(this) == null || this.guild == null) + return this.buyPercent; + NPCProfits profits = NPC.GetNPCProfits(this); + if (player.getGuild().equals(this.guild)) + return profits.buyGuild; + if (player.getGuild().getNation().equals(this.guild.getNation())) + return profits.buyNation; + + return profits.buyNormal; + } + + public float getSellPercent(PlayerCharacter player) { + if (NPC.GetNPCProfits(this) == null || this.guild == null) + return 1 + this.sellPercent; + NPCProfits profits = NPC.GetNPCProfits(this); + if (player.getGuild().equals(this.guild)) + return 1 + profits.sellGuild; + if (player.getGuild().getNation().equals(this.guild.getNation())) + return 1 + profits.sellNation; + + return 1 + profits.sellNormal; + } + + @Override + public boolean canBeLooted() { + return !this.isAlive(); + } + + public ArrayList getModTypeTable() { + return this.modTypeTable; + } + + @Override + public void updateDatabase() { + DbManager.NPCQueries.updateDatabase(this); + } + + public int getSymbol() { + return symbol; + } + + public ArrayList getModSuffixTable() { + return modSuffixTable; + } + + public ArrayList getItemModTable() { + return itemModTable; + } + + public boolean isRanking() { + + return this.upgradeDateTime != null; + } + + @Override + public void runAfterLoad() { + + if (ConfigManager.serverType.equals(ServerType.LOGINSERVER)) + return; + + this.contract = DbManager.ContractQueries.GET_CONTRACT(this.contractUUID); + + if (this.equipmentSetID == 0 && this.contract != null) + this.equipmentSetID = this.contract.equipmentSet; + + // Default to contract load ID + + if (loadID == 0) { + + if (this.contract != null) + loadID = this.contract.getMobbaseID(); + else { + Logger.error("Invalid contract for NPC: " + this.getObjectUUID()); + loadID = 2100; // Default human male + } + } + + this.mobBase = MobBase.getMobBase(this.loadID); + + this.building = BuildingManager.getBuilding(this.buildingUUID); + + if (this.building != null) + this.guild = this.building.getGuild(); + else + this.guild = Guild.getGuild(this.guildUUID); + + + if (this.guild == null) + this.guild = Guild.getErrantGuild(); + + if (this.contract == null) + return; // Early exit for npc guild owners + + // Name override for this npc + + if (wordCount(this.name) < 2 && this.contract != null) + this.name += " the " + this.contract.getName(); + + // Initialize inventory + + this.charItemManager = new CharacterItemManager(this); + + // Configure parent zone adding this NPC to the + // zone collection + + this.parentZone = ZoneManager.getZoneByUUID(this.parentZoneUUID); + this.parentZone.zoneNPCSet.remove(this); + this.parentZone.zoneNPCSet.add(this); + + // Setup location for this NPC + + this.bindLoc = new Vector3fImmutable(this.statLat, this.statAlt, this.statLon); + this.bindLoc = this.parentZone.getLoc().add(this.bindLoc); + this.loc = new Vector3fImmutable(bindLoc); + + // Handle NPCs within buildings + + if (this.building != null) + NPCManager.slotCharacterInBuilding(this); + + if (this.contract != null) { + this.symbol = this.contract.getIconID(); + this.modTypeTable = this.contract.getNPCModTypeTable(); + this.modSuffixTable = this.contract.getNpcModSuffixTable(); + this.itemModTable = this.contract.getItemModTable(); + int VID = this.contract.getVendorID(); + + if (VID != 0) + this.vendorID = VID; + else + this.vendorID = 1; //no vendor items + } + + if (this.mobBase != null) { + this.healthMax = this.mobBase.getHealthMax(); + this.manaMax = 0; + this.staminaMax = 0; + this.setHealth(this.healthMax); + this.mana.set(this.manaMax); + this.stamina.set(this.staminaMax); + } + + if (this.parentZone.isPlayerCity()) + if (NPC.GetNPCProfits(this) == null) + NPCProfits.CreateProfits(this); + + //TODO set these correctly later + this.rangeHandOne = 8; + this.rangeHandTwo = -1; + this.minDamageHandOne = 1; + this.maxDamageHandOne = 4; + this.minDamageHandTwo = 1; + this.maxDamageHandTwo = 4; + this.atrHandOne = 300; + this.defenseRating = 200; + this.isActive = true; + + this.charItemManager.load(); + + this.equip = loadEquipmentSet(this.equipmentSetID); + + if (this.equip == null) + this.equip = new HashMap<>(); + + try { + + DbManager.NPCQueries.LOAD_ALL_ITEMS_TO_PRODUCE(this); + + for (ProducedItem producedItem : this.forgedItems) { + MobLoot ml = new MobLoot(this, ItemBase.getItemBase(producedItem.getItemBaseID()), false); + + DbManager.NPCQueries.UPDATE_ITEM_ID(producedItem.getID(), currentID, ml.getObjectUUID()); + + if (producedItem.isInForge()) { + + if (producedItem.getPrefix() != null && !producedItem.getPrefix().isEmpty()) { + ml.addPermanentEnchantment(producedItem.getPrefix(), 0, 0, true); + ml.setPrefix(producedItem.getPrefix()); + } + + if (producedItem.getSuffix() != null && !producedItem.getSuffix().isEmpty()) { + ml.addPermanentEnchantment(producedItem.getSuffix(), 0, 0, false); + ml.setSuffix(producedItem.getSuffix()); + } + + if (!producedItem.isRandom()) + ml.setIsID(true); + + ml.loadEnchantments(); + + ml.setValue(producedItem.getValue()); + ml.setDateToUpgrade(producedItem.getDateToUpgrade().getMillis()); + ml.containerType = Enum.ItemContainerType.FORGE; + this.addItemToForge(ml); + + } else { + + if (producedItem.getPrefix() != null && !producedItem.getPrefix().isEmpty()) { + ml.addPermanentEnchantment(producedItem.getPrefix(), 0, 0, true); + ml.setPrefix(producedItem.getPrefix()); + } + + if (producedItem.getSuffix() != null && !producedItem.getSuffix().isEmpty()) { + ml.addPermanentEnchantment(producedItem.getSuffix(), 0, 0, false); + ml.setSuffix(producedItem.getSuffix()); + } + + ml.setDateToUpgrade(producedItem.getDateToUpgrade().getMillis()); + ml.containerType = Enum.ItemContainerType.INVENTORY; + ml.setIsID(true); + + this.charItemManager.addItemToInventory(ml); + } + ml.setValue(producedItem.getValue()); + } + + // Create NPC bounds object + Bounds npcBounds = Bounds.borrow(); + npcBounds.setBounds(this.getLoc()); + + } catch (Exception e) { + Logger.error(e.getMessage()); + } + } + + public void removeFromZone() { + this.parentZone.zoneNPCSet.remove(this); + } + + @Override + protected ConcurrentHashMap initializePowers() { + return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + } + + public DateTime getUpgradeDateTime() { + lock.readLock().lock(); + try { + return upgradeDateTime; + } finally { + lock.readLock().unlock(); + } + } + + public void setUpgradeDateTime(DateTime upgradeDateTime) { + + if (!DbManager.NPCQueries.updateUpgradeTime(this, upgradeDateTime)) { + Logger.error("Failed to set upgradeTime for building " + currentID); + return; + } + + this.upgradeDateTime = upgradeDateTime; + } + + public ArrayList getRolling() { + synchronized (rolling) { + return rolling; + } + } + + public int getRollingCount() { + synchronized (this.rolling) { + return rolling.size(); + } + } + + public void addItemToForge(MobLoot item) { + synchronized (this.rolling) { + this.rolling.add(item); + } + } + + public void removeItemFromForge(Item item) { + synchronized (this.rolling) { + this.rolling.remove(item); + } + } + + @Override + public Guild getGuild() { + if (this.building != null) + return building.getGuild(); + return this.guild; + } + + public HashSet getCanRoll() { + + if (this.canRoll == null) + this.canRoll = DbManager.ItemQueries.GET_ITEMS_FOR_VENDOR(this.vendorID); + + HashSet fullItemList = this.canRoll; + HashSet filteredItemList = new HashSet<>(); + short maxSkill = 25; + + switch (this.getRank()) { + case 1: + maxSkill = 25; + break; + case 2: + maxSkill = 50; + break; + case 3: + case 4: + maxSkill = 75; + break; + case 5: + case 6: + maxSkill = 100; + break; + case 7: + maxSkill = 110; + break; + } + ItemBase itemBase; + for (Integer itemID : fullItemList) { + itemBase = ItemBase.getItemBase(itemID); + boolean exclude = itemBase.getPercentRequired() == 0 && itemBase.getType() == ItemType.WEAPON; + if (itemBase.getPercentRequired() <= maxSkill && !exclude) + filteredItemList.add(itemID); + } + if (this.contract.getVendorID() == 102) { + + for (int i = 0; i < this.getRank(); i++) { + int subID = i + 1; + filteredItemList.add(910010 + subID); + } + + if (this.getRank() == 7) + filteredItemList.add(910018); + } + + return filteredItemList; + } + + public int getRollingTimeInSeconds(int itemID) { + + ItemBase ib = ItemBase.getItemBase(itemID); + + if (ib == null) + return 0; + + if (ib.getType() == ItemType.SCROLL) + return this.getRank() * 60 * 60 * 3; + + float time; + + if (this.building == null) + return 600; + + float rank = this.building.getRank() - 1; + float rate = (float) (2.5 * rank); + time = (20 - rate); + time *= 60; + return (int) time; + } + + public ConcurrentHashMap getSiegeMinionMap() { + return siegeMinionMap; + } + + public boolean remove() { + + Building building; + + // Remove npc from it's building + + building = this.building; + + if (building != null) { + building.getHirelings().remove(this); + this.removeMinions(); + } + + // Delete npc from database + + if (DbManager.NPCQueries.DELETE_NPC(this) == 0) { + return false; + } + + // Remove npc from the simulation + + this.removeFromCache(); + WorldGrid.RemoveWorldObject(this); + WorldGrid.removeObject(this); + return true; + } + + public int getUpgradeCost() { + + int upgradeCost; + + upgradeCost = Integer.MAX_VALUE; + + if (this.getRank() < 7) + return (this.getRank() * 100650) + 21450; + + return upgradeCost; + } + + public int getUpgradeTime() { + + int upgradeTime; // expressed in hours + + upgradeTime = Integer.MAX_VALUE; + + if (this.getRank() < 7) + return (this.getRank() * 8); + + return 0; + } + + public synchronized Item produceItem(int playerID, int amount, boolean isRandom, int pToken, int sToken, String customName, int itemID) { + + Zone serverZone; + City city; + Item item = null; + + PlayerCharacter player = null; + + if (playerID != 0) + player = SessionManager.getPlayerCharacterByID(playerID); + + try { + + if (this.getRollingCount() >= this.getRank()) { + + if (player != null) + ChatManager.chatSystemInfo(player, this.getName() + " " + this.getContract().getName() + " slots are full"); + + return null; + } + + // Cannot roll items without a warehouse. + // Due to the fact fillForge references the + // warehouse and early exits. *** Refactor??? + + serverZone = this.building.getParentZone(); + + if (serverZone == null) + return null; + + city = City.GetCityFromCache(serverZone.getPlayerCityUUID()); + + if (city == null) { + + if (player != null) + ErrorPopupMsg.sendErrorMsg(player, "Could not find city."); // Production denied: This building must be protected to gain access to warehouse resources. + + return null; + } + + if (this.building == null) { + + if (player != null) + ErrorPopupMsg.sendErrorMsg(player, "Could not find building."); // Production denied: This building must be protected to gain ac + + return null; + } + + //TODO create Normal Items. + + if (amount == 0) + amount = 1; + + if (isRandom) + item = ItemFactory.randomRoll(this, player, amount, itemID); + else + item = ItemFactory.fillForge(this, player, amount, itemID, pToken, sToken, customName); + + if (item == null) + return null; + + ItemProductionMsg outMsg = new ItemProductionMsg(this.building, this, item, 8, true); + DispatchMessage.dispatchMsgToInterestArea(this, outMsg, DispatchChannel.SECONDARY, 700, false, false); + + } catch (Exception e) { + Logger.error(e); + } + return item; + } + + public synchronized boolean completeItem(int itemUUID) { + + MobLoot targetItem; + + try { + targetItem = MobLoot.getFromCache(itemUUID); + + if (targetItem == null) + return false; + + if (!this.getCharItemManager().forgeContains(targetItem, this)) + return false; + + if (!DbManager.NPCQueries.UPDATE_ITEM_TO_INVENTORY(targetItem.getObjectUUID(), currentID)) + return false; + + targetItem.setIsID(true); + + this.rolling.remove(targetItem); + this.getCharItemManager().addItemToInventory(targetItem); + + //remove from client forge window + + ItemProductionMsg outMsg1 = new ItemProductionMsg(this.building, this, targetItem, 9, true); + DispatchMessage.dispatchMsgToInterestArea(this, outMsg1, DispatchChannel.SECONDARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false); + ItemProductionMsg outMsg = new ItemProductionMsg(this.building, this, targetItem, 10, true); + DispatchMessage.dispatchMsgToInterestArea(this, outMsg, DispatchChannel.SECONDARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false); + + } catch (Exception e) { + Logger.error(e.getMessage()); + } + return true; + } + + public HashMap getEquip() { + return equip; + } + + public int getEquipmentSetID() { + return equipmentSetID; + } + + public String getNameOverride() { + return name; + } + + public int getRepairCost() { + return repairCost; + } + + public void setRepairCost(int repairCost) { + this.repairCost = repairCost; + } + + public void processUpgradeNPC(PlayerCharacter player) { + + int rankCost; + Building building; + DateTime dateToUpgrade; + + + this.lock.writeLock().lock(); + try { + + + building = this.getBuilding(); + + // Cannot upgrade an npc not within a building + + if (building == null) + return; + + // Cannot upgrade an npc at max rank + + if (this.getRank() == 7) + return; + + // Cannot upgrade an npc who is currently ranking + + if (this.isRanking()) + return; + + rankCost = this.getUpgradeCost(); + + // SEND NOT ENOUGH GOLD ERROR + + if (!building.hasFunds(rankCost)) { + sendErrorPopup(player, 127); + return; + } + + if (rankCost > building.getStrongboxValue()) { + sendErrorPopup(player, 127); + return; + } + + try { + + if (!building.transferGold(-rankCost, false)) + return; + + dateToUpgrade = DateTime.now().plusHours(this.getUpgradeTime()); + + this.setUpgradeDateTime(dateToUpgrade); + + // Schedule upgrade job + + this.submitUpgradeJob(); + + } catch (Exception e) { + PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); + } + } catch (Exception e) { + Logger.error(e); + } finally { + this.lock.writeLock().unlock(); + } + } + +} diff --git a/src/engine/objects/NPCProfits.java b/src/engine/objects/NPCProfits.java new file mode 100644 index 00000000..280be130 --- /dev/null +++ b/src/engine/objects/NPCProfits.java @@ -0,0 +1,101 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.ProfitType; +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + +public class NPCProfits { + + + public static NPCProfits defaultProfits = new NPCProfits(0, .33f, .33f, .33f, 1, 1, 1); + public static HashMap ProfitCache = new HashMap<>(); + public int npcUID; + public float buyNormal; + public float buyGuild; + public float buyNation; + public float sellNormal; + public float sellGuild; + public float sellNation; + + /** + * ResultSet Constructor + */ + + public NPCProfits(ResultSet rs) throws SQLException { + + npcUID = rs.getInt("npcUID"); + buyNormal = rs.getFloat("buy_normal"); + buyGuild = rs.getFloat("buy_guild"); + buyNation = rs.getFloat("buy_nation"); + sellNormal = rs.getFloat("sell_normal"); + sellGuild = rs.getFloat("sell_guild"); + sellNation = rs.getFloat("sell_nation"); + + } + + public NPCProfits(int npcUID, float buyNormal, float buyGuild, float buyNation, float sellNormal, + float sellGuild, float sellNation) { + super(); + this.npcUID = npcUID; + this.buyNormal = buyNormal; + this.buyGuild = buyGuild; + this.buyNation = buyNation; + this.sellNormal = sellNormal; + this.sellGuild = sellGuild; + this.sellNation = sellNation; + } + + public static boolean UpdateProfits(NPC npc, NPCProfits profit, ProfitType profitType, float value) { + + try { + if (!DbManager.NPCQueries.UPDATE_PROFITS(npc, profitType, value)) + return false; + } catch (Exception e) { + e.printStackTrace(); + } + + switch (profitType) { + case BuyNormal: + profit.buyNormal = value; + break; + case BuyGuild: + profit.buyGuild = value; + break; + case BuyNation: + profit.buyNation = value; + break; + case SellNormal: + profit.sellNormal = value; + break; + case SellGuild: + profit.sellGuild = value; + break; + case SellNation: + profit.sellNation = value; + break; + + } + return true; + } + + + public static boolean CreateProfits(NPC npc) { + DbManager.NPCQueries.CREATE_PROFITS(npc); + NPCProfits profits = new NPCProfits(npc.getObjectUUID(), .33f, .33f, .33f, 1, 1, 1); + NPCProfits.ProfitCache.put(npc.getObjectUUID(), profits); + return true; + } + +} diff --git a/src/engine/objects/NPCRune.java b/src/engine/objects/NPCRune.java new file mode 100644 index 00000000..ed17749d --- /dev/null +++ b/src/engine/objects/NPCRune.java @@ -0,0 +1,120 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + + +public class NPCRune extends AbstractGameObject { + + private final RuneBase runeBase; + private final int player; + private final ArrayList skillsGranted; + private final ArrayList effectsGranted; + + /** + * No Table ID Constructor + */ + public NPCRune(RuneBase runeBase, int characterID) { + super(); + this.runeBase = runeBase; + this.player = characterID; + if (this.runeBase != null) + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); + else + this.skillsGranted = new ArrayList<>(); + this.effectsGranted = new ArrayList<>(); + } + + /** + * Normal Constructor + */ + public NPCRune(RuneBase runeBase, int characterID, int newUUID) { + super(newUUID); + this.runeBase = runeBase; + this.player = characterID; + if (this.runeBase == null) + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); + else + this.skillsGranted = new ArrayList<>(); + this.effectsGranted = new ArrayList<>(); + } + + /** + * ResultSet Constructor + */ + public NPCRune(ResultSet rs) throws SQLException { + super(rs); + + this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID")); + this.player = rs.getInt("NpcID"); + if (this.runeBase != null) { + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); + this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID()); + } else { + Logger.error("Failed to find RuneBase for NPCRune " + this.getObjectUUID()); + this.skillsGranted = new ArrayList<>(); + this.effectsGranted = new ArrayList<>(); + } + } + + public static void serializeForClientMsg(NPCRune npcRune, ByteBufferWriter writer) { + if (npcRune.runeBase != null) { + writer.putInt(npcRune.runeBase.getType()); + writer.putInt(0); + writer.putInt(npcRune.runeBase.getObjectUUID()); + writer.putInt(npcRune.getObjectType().ordinal()); + writer.putInt(npcRune.getObjectUUID()); + } else { + for (int i = 0; i < 5; i++) + writer.putInt(0); + } + } + + /* + * Getters + */ + public RuneBase getRuneBase() { + return this.runeBase; + } + + public int getRuneBaseID() { + if (this.runeBase != null) + return this.runeBase.getObjectUUID(); + return 0; + } + + public int getPlayerID() { + return this.player; + } + + public ArrayList getSkillsGranted() { + return this.skillsGranted; + } + + /* + * Serializing + */ + + public ArrayList getEffectsGranted() { + return this.effectsGranted; + } + + @Override + public void updateDatabase() { + + } +} diff --git a/src/engine/objects/Nation.java b/src/engine/objects/Nation.java new file mode 100644 index 00000000..94719e6d --- /dev/null +++ b/src/engine/objects/Nation.java @@ -0,0 +1,120 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.net.ByteBufferWriter; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class Nation extends AbstractWorldObject { + + /* + * Utils + */ + private static Nation n; + private final String name; + private GuildTag gt; + private String motd = ""; + private int primaryGuildID = 0; + + /** + * No Id Constructor + */ + public Nation(String name, GuildTag gt) { + super(); + this.name = name; + this.gt = gt; + } + + /** + * Normal Constructor + */ + public Nation(String name, GuildTag gt, int newUUID) { + super(newUUID); + this.name = name; + this.gt = gt; + } + + /** + * ResultSet Constructor + */ + public Nation(ResultSet rs) throws SQLException { + super(rs); + + this.name = rs.getString("name"); + + this.gt = new GuildTag(rs.getInt("backgroundColor01"), + rs.getInt("backgroundColor02"), + rs.getInt("symbolColor"), + rs.getInt("symbol"), + rs.getInt("backgroundDesign")); + this.motd = rs.getString("motd"); + this.primaryGuildID = rs.getInt("primaryGuild"); + } + + public static Nation getErrantNation() { + if (n == null) { + n = new Nation("None", GuildTag.ERRANT, 0); + } + return n; + } + + public static void serializeForTrack(Nation nation, ByteBufferWriter writer) { + writer.putInt(nation.getObjectType().ordinal()); + writer.putInt(nation.getObjectUUID()); + writer.put((byte) 1); + GuildTag._serializeForDisplay(nation.gt, writer); + } + + /* + * Getters + */ + @Override + public String getName() { + return this.name; + } + + public GuildTag getGuildTag() { + return this.gt; + } + + public String getMOTD() { + return this.motd; + } + + public void setMOTD(String value) { + this.motd = value; + } + + public int getPrimaryGuildID() { + return this.primaryGuildID; + } + + + + /* + * Serialization + */ + + public void setPrimaryGuildID(int value) { + this.primaryGuildID = value; + } + + @Override + public void updateDatabase() { + + } + + @Override + public void runAfterLoad() { + } +} diff --git a/src/engine/objects/Petition.java b/src/engine/objects/Petition.java new file mode 100644 index 00000000..fb8fe046 --- /dev/null +++ b/src/engine/objects/Petition.java @@ -0,0 +1,31 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.objects; +import engine.math.Vector3fImmutable; +import engine.net.client.ClientConnection; +import engine.net.client.msg.PetitionReceivedMsg; + +public class Petition { + public int primaryType; + public int subType; + public Account reportAccount; + public PlayerCharacter reportPlayer; + public Vector3fImmutable playerLocation; + public String message; + + public Petition(PetitionReceivedMsg petitionReceivedMsg, ClientConnection origin) { + this.primaryType = petitionReceivedMsg.type; + this.subType = petitionReceivedMsg.subType; + this.reportAccount = origin.getAccount(); + this.reportPlayer = origin.getPlayerCharacter(); + this.playerLocation = origin.getPlayerCharacter().getLoc(); + this.message = petitionReceivedMsg.message; + } + +} diff --git a/src/engine/objects/PlayerBonuses.java b/src/engine/objects/PlayerBonuses.java new file mode 100644 index 00000000..5a8ad2ed --- /dev/null +++ b/src/engine/objects/PlayerBonuses.java @@ -0,0 +1,491 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.ChatManager; +import engine.gameManager.ConfigManager; +import engine.gameManager.PowersManager; +import engine.powers.DamageShield; +import engine.powers.EffectsBase; +import engine.powers.effectmodifiers.AbstractEffectModifier; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + + +public class PlayerBonuses { + + //First bonus set + private ConcurrentHashMap bonusFloats = new ConcurrentHashMap<>(); + private ConcurrentHashMap bonusDamageShields = new ConcurrentHashMap<>(); + private ConcurrentHashMap bonusStrings = new ConcurrentHashMap<>(); + private ConcurrentHashMap> bonusLists = new ConcurrentHashMap<>(); + private ConcurrentHashMap> bonusBools = new ConcurrentHashMap<>(); + private ConcurrentHashMap skillBonuses = new ConcurrentHashMap<>(); + private ConcurrentHashMap regens = new ConcurrentHashMap<>(); + + //If active == 0 then all gets come from the A list and all puts go to the B list + //If active == 1 then all gets come from the B list and all puts go to the A list + //They alternate each time bonuses are calculated so the one being updated isn't read from. + + + /** + * Generic Constructor + */ + public PlayerBonuses(PlayerCharacter pc) { + } + + public PlayerBonuses(Mob mob) { + clearRuneBaseEffects(); + } + + public static void InitializeBonuses(PlayerCharacter player) { + if (player.bonuses == null) + return; + if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) + return; + + player.bonuses.calculateRuneBaseEffects(player); + } + + public static PlayerBonuses grantBonuses(AbstractCharacter ac) { + + if (ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) + return new PlayerBonuses((PlayerCharacter) ac); + else if (ac.getObjectType().equals(Enum.GameObjectType.Mob)) + return new PlayerBonuses((Mob) ac); + else + return null; + } + + public void clearRuneBaseEffects() { + + this.bonusBools.clear(); + this.bonusFloats.clear(); + this.bonusStrings.clear(); + this.bonusDamageShields.clear(); + this.bonusLists.clear(); + this.skillBonuses.clear(); + this.regens.put(ModType.HealthRecoverRate, (float) 1); + this.regens.put(ModType.ManaRecoverRate, (float) 1); + this.regens.put(ModType.StaminaRecoverRate, (float) 1); + } + + + public void calculateRuneBaseEffects(PlayerCharacter pc) { + //Clear everything + clearRuneBaseEffects(); + + //recalculate race + + + if (pc.getRace() != null) { + + + if (pc.getRace().getEffectsList() != null) + for (MobBaseEffects raceEffect : pc.getRace().getEffectsList()) { + EffectsBase eb = PowersManager.getEffectByToken(raceEffect.getToken()); + + if (eb == null) + continue; + if (pc.getLevel() < raceEffect.getReqLvl()) + continue; + for (AbstractEffectModifier modifier : eb.getModifiers()) { + modifier.applyBonus(pc, raceEffect.getRank()); + } + + } + + if (SkillsBase.runeSkillsCache.containsKey(pc.getRaceID())) { + for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getRaceID()).keySet()) { + float amount = SkillsBase.runeSkillsCache.get(pc.getRaceID()).get(skillToken); + + SkillsBase sb = SkillsBase.tokenCache.get(skillToken); + + if (sb == null) + continue; + if (this.skillBonuses.containsKey(sb.sourceType) == false) + this.skillBonuses.put(sb.sourceType, amount); + else + this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount); + + } + } + } + + //calculate baseclass effects + if (pc.getBaseClass() != null) { + + if (pc.getBaseClass().getEffectsList() != null) + for (MobBaseEffects classEffect : pc.getBaseClass().getEffectsList()) { + EffectsBase eb = PowersManager.getEffectByToken(classEffect.getToken()); + + if (eb == null) + continue; + if (pc.getLevel() < classEffect.getReqLvl()) + continue; + for (AbstractEffectModifier modifier : eb.getModifiers()) { + modifier.applyBonus(pc, classEffect.getRank()); + } + + } + + if (SkillsBase.runeSkillsCache.containsKey(pc.getBaseClassID())) { + for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getBaseClassID()).keySet()) { + float amount = SkillsBase.runeSkillsCache.get(pc.getBaseClassID()).get(skillToken); + + SkillsBase sb = SkillsBase.tokenCache.get(skillToken); + + if (sb == null) + continue; + if (this.skillBonuses.containsKey(sb.sourceType) == false) + this.skillBonuses.put(sb.sourceType, amount); + else + this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount); + } + } + + } + + //calculate promotionClass Effects + if (pc.getPromotionClass() != null) { + if (pc.getPromotionClass().getEffectsList() != null) + for (MobBaseEffects promoEffect : pc.getPromotionClass().getEffectsList()) { + EffectsBase eb = PowersManager.getEffectByToken(promoEffect.getToken()); + + if (eb == null) + continue; + if (pc.getLevel() < promoEffect.getReqLvl()) + continue; + for (AbstractEffectModifier modifier : eb.getModifiers()) { + modifier.applyBonus(pc, promoEffect.getRank()); + } + + } + + if (SkillsBase.runeSkillsCache.containsKey(pc.getPromotionClassID())) { + for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getPromotionClassID()).keySet()) { + float amount = SkillsBase.runeSkillsCache.get(pc.getPromotionClassID()).get(skillToken); + + SkillsBase sb = SkillsBase.tokenCache.get(skillToken); + + if (sb == null) + continue; + if (this.skillBonuses.containsKey(sb.sourceType) == false) + this.skillBonuses.put(sb.sourceType, amount); + else + this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount); + + } + } + + } + + for (CharacterRune runes : pc.getRunes()) { + RuneBase characterRune = RuneBase.getRuneBase(runes.getRuneBaseID()); + + if (characterRune.getEffectsList() != null) + for (MobBaseEffects runeEffect : characterRune.getEffectsList()) { + EffectsBase eb = PowersManager.getEffectByToken(runeEffect.getToken()); + + if (eb == null) + continue; + if (pc.getLevel() < runeEffect.getReqLvl()) + continue; + for (AbstractEffectModifier modifier : eb.getModifiers()) { + modifier.applyBonus(pc, runeEffect.getRank()); + } + + } + + if (SkillsBase.runeSkillsCache.containsKey(runes.getRuneBaseID())) { + for (int skillToken : SkillsBase.runeSkillsCache.get(runes.getRuneBaseID()).keySet()) { + float amount = SkillsBase.runeSkillsCache.get(runes.getRuneBaseID()).get(skillToken); + + SkillsBase sb = SkillsBase.tokenCache.get(skillToken); + + if (sb == null) + continue; + if (this.skillBonuses.containsKey(sb.sourceType) == false) + this.skillBonuses.put(sb.sourceType, amount); + else + this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount); + + } + } + + } + + //Update seeInvis if needed + + float seeInvis = this.getFloat(ModType.SeeInvisible, SourceType.None); + if (pc.getSeeInvis() < seeInvis) + pc.setSeeInvis((short) seeInvis); + + } + + + public void grantEffect(RuneBaseEffect rbe) { + } + + + public void setFloat(AbstractEffectModifier mod, float val) { + if (val != 0) + this.bonusFloats.put(mod, val); + else + this.bonusFloats.remove(mod); + } + + public void setString(AbstractEffectModifier mod, String val) { + if (!val.isEmpty()) + this.bonusStrings.put(mod, val); + else + this.bonusStrings.remove(mod); + } + + public void setList(ModType mod, HashSet val) { + if (!val.equals(null)) + this.bonusLists.put(mod, val); + else + this.bonusLists.remove(mod); + } + + + public void addFloat(AbstractEffectModifier mod, Float val) { + if (this.bonusFloats.containsKey(mod) == false) + this.bonusFloats.put(mod, val); + else + this.bonusFloats.put(mod, this.bonusFloats.get(mod) + val); + } + + public void multFloat(AbstractEffectModifier mod, Float val) { + if (this.bonusFloats.containsKey(mod) == false) + this.bonusFloats.put(mod, val); + else + this.bonusFloats.put(mod, this.bonusFloats.get(mod) + (val * (this.bonusFloats.get(mod) + val))); + } + + public void multRegen(ModType mod, Float val) { + this.regens.put(mod, this.regens.get(mod) + (this.regens.get(mod) * val)); + } + + + public boolean getBool(ModType modType, SourceType sourceType) { + + if (this.bonusBools.containsKey(modType) == false) + return false; + + if (this.bonusBools.get(modType).containsKey(sourceType) == false) + return false; + + return this.bonusBools.get(modType).get(sourceType); + + } + + public float getSkillBonus(SourceType sourceType) { + + if (this.skillBonuses.containsKey(sourceType) == false) + return 0; + return this.skillBonuses.get(sourceType); + } + + + public float getFloat(ModType modType, SourceType sourceType) { + float amount = 0; + for (AbstractEffectModifier mod : this.bonusFloats.keySet()) { + if (mod.getPercentMod() != 0) + continue; + if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false) + continue; + + if (this.bonusFloats.get(mod) == null) + continue; + + amount += this.bonusFloats.get(mod); + } + return amount; + } + + public float getFloatPercentPositive(ModType modType, SourceType sourceType) { + float amount = 0; + for (AbstractEffectModifier mod : this.bonusFloats.keySet()) { + + if (mod.getPercentMod() == 0 && !modType.equals(ModType.AdjustAboveDmgCap)) + continue; + + + if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false) + continue; + + + if (this.bonusFloats.get(mod) == null) + continue; + + if (this.bonusFloats.get(mod) < 0) + continue; + amount += this.bonusFloats.get(mod); + } + + return amount; + } + + public float getFloatPercentAll(ModType modType, SourceType sourceType) { + float amount = 0; + for (AbstractEffectModifier mod : this.bonusFloats.keySet()) { + + if (mod.getPercentMod() == 0 && !modType.equals(ModType.AdjustAboveDmgCap)) + continue; + + + if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false) + continue; + + if (this.bonusFloats.get(mod) == null) + continue; + + amount += this.bonusFloats.get(mod); + } + + return amount; + } + + public float getRegen(ModType modType) { + return this.regens.get(modType); + } + + + public float getFloatPercentNullZero(ModType modType, SourceType sourceType) { + float amount = 0; + for (AbstractEffectModifier mod : this.bonusFloats.keySet()) { + + if (mod.getPercentMod() == 0) + continue; + if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false) + continue; + + if (this.bonusFloats.get(mod) == null) + continue; + + amount += this.bonusFloats.get(mod); + } + return amount; + } + + public float getFloatPercentNegative(ModType modType, SourceType sourceType) { + float amount = 0; + for (AbstractEffectModifier mod : this.bonusFloats.keySet()) { + + if (mod.getPercentMod() == 0) + continue; + if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false) + continue; + + if (this.bonusFloats.get(mod) == null) + continue; + + if (this.bonusFloats.get(mod) > 0) + continue; + + + amount += this.bonusFloats.get(mod); + } + return amount; + } + + + public HashSet getList(ModType modType) { + if (this.bonusLists.containsKey(modType)) + return this.bonusLists.get(modType); + else + return null; + } + + public ConcurrentHashMap getDamageShields() { + return this.bonusDamageShields; + } + + public void addDamageShield(AbstractEffectModifier mod, DamageShield ds) { + this.bonusDamageShields.put(mod, ds); + } + + + public void updateIfHigher(AbstractEffectModifier mod, Float val) { + + if (this.bonusFloats.containsKey(mod) == false) { + this.bonusFloats.put(mod, val); + return; + } + float oldVal = this.getFloat(mod.modType, mod.sourceType); + + if (oldVal > val) + return; + + this.bonusFloats.put(mod, val); + + } + + + //Read maps + + public void printBonusesToClient(PlayerCharacter pc) { + + + for (ModType modType : this.bonusBools.keySet()) { + for (SourceType sourceType : this.bonusBools.get(modType).keySet()) { + ChatManager.chatSystemInfo(pc, modType.name() + "-" + sourceType.name() + " = " + this.bonusBools.get(modType).get(sourceType)); + } + } + + for (ModType modType : ModType.values()) { + + if (modType.equals(ModType.StaminaRecoverRate) || modType.equals(ModType.HealthRecoverRate) || modType.equals(ModType.ManaRecoverRate)) + ChatManager.chatSystemInfo(pc, modType.name() + " = " + this.getRegen(modType)); + else + for (SourceType sourceType : SourceType.values()) { + float amount = this.getFloat(modType, sourceType); + float percentAmount = this.getFloatPercentPositive(modType, sourceType); + float percentAmountNegative = this.getFloatPercentNegative(modType, sourceType); + + if (amount != 0) + ChatManager.chatSystemInfo(pc, modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + amount); + + if (percentAmount != 0) + ChatManager.chatSystemInfo(pc, "Percent : " + modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + percentAmount); + + if (percentAmountNegative != 0) + ChatManager.chatSystemInfo(pc, "Negative Percent : " + modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + percentAmountNegative); + + } + } + + } + + + public void setBool(ModType modType, SourceType sourceType, boolean val) { + if (val == true) { + + if (this.bonusBools.get(modType) == null) { + HashMap sourceMap = new HashMap<>(); + this.bonusBools.put(modType, sourceMap); + } + + this.bonusBools.get(modType).put(sourceType, val); + return; + } + + if (this.bonusBools.containsKey(modType)) + this.bonusBools.get(modType).remove(sourceType); + } + +} diff --git a/src/engine/objects/PlayerCharacter.java b/src/engine/objects/PlayerCharacter.java new file mode 100644 index 00000000..35dd8c5a --- /dev/null +++ b/src/engine/objects/PlayerCharacter.java @@ -0,0 +1,5529 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.*; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.InterestManager; +import engine.InterestManagement.RealmMap; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.CharacterRecord; +import engine.db.archive.DataWarehouse; +import engine.db.archive.PvpRecord; +import engine.exception.MsgSendException; +import engine.exception.SerializationException; +import engine.gameManager.*; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.DeferredPowerJob; +import engine.jobs.FinishSpireEffectJob; +import engine.jobs.NoTimeJob; +import engine.math.Bounds; +import engine.math.FastMath; +import engine.math.Vector3fImmutable; +import engine.net.ByteBufferWriter; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.net.client.msg.login.CommitNewCharacterMsg; +import engine.powers.EffectsBase; +import engine.server.MBServerStatics; +import engine.server.login.LoginServer; +import engine.server.login.LoginServerMsgHandler; +import engine.server.world.WorldServer; +import engine.util.MiscUtils; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +public class PlayerCharacter extends AbstractCharacter { + + //This object is to be used as the lock in a synchronized statement + //any time the name of a PlayerCharacter needs to be set or + //changed. It ensures the uniqueness check and subsequent + //database update can happen exclusively. + private static final Object FirstNameLock = new Object(); + public final ReadWriteLock respawnLock = new ReentrantReadWriteLock(true); + public final ArrayList necroPets = new ArrayList<>(); + private final Account account; + private final Race race; + private final byte skinColor; + private final byte hairColor; + private final byte beardColor; + private final byte hairStyle; + private final byte beardStyle; + //All Guild information should be held here + private final AtomicInteger guildStatus; + private final AtomicInteger strMod = new AtomicInteger(); // Stat Modifiers + private final AtomicInteger dexMod = new AtomicInteger(); + private final AtomicInteger conMod = new AtomicInteger(); + private final AtomicInteger intMod = new AtomicInteger(); + private final AtomicInteger spiMod = new AtomicInteger(); + private final ReadWriteLock teleportLock = new ReentrantReadWriteLock(true); + private final HashMap summoners = new HashMap<>(); + private final HashSet loadedObjects = new HashSet<>(); + private final ConcurrentHashMap> chatChanFloodList = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final ConcurrentHashMap killMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final AtomicInteger trainsAvailable = new AtomicInteger(0); // num skill trains not used + + public boolean notDeleted; // <-Use this for deleting character + // =========================================== + // Variables NOT to put into the database!!!! (session only) + // =========================================== + public short statStrMax; // Max Base Stats + public short statDexMax; + public short statConMax; + public short statIntMax; + public short statSpiMax; + public short statStrMin; // Min Base Stats + public short statDexMin; + public short statConMin; + public short statIntMin; + public short statSpiMin; + // Current Stats before Equip and Effect + // Modifiers + public short statStrBase; + public short statDexBase; + public short statConBase; + public short statIntBase; + public short statSpiBase; + public short trainedStatPoints = 0; + public boolean isCSR = false; + //TODO Public fields break OO!!! + public boolean newChar; + public LinkedList pvpKills; + public LinkedList pvpDeaths; + public int lastBuildingAccessed = 0; + public boolean RUN_MAGICTREK = true; + public float centerHeight = 0; + public float landingAltitude = 0; + public int bindBuilding = 0; + public FriendStatus friendStatus = FriendStatus.Available; + protected ArrayList runes; + private BaseClass baseClass; + private PromotionClass promotionClass; + private long channelMute = 0; // none muted. + private ConcurrentHashMap ignoredPlayerIDs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private boolean lfGroup = false; + private boolean lfGuild = false; + private boolean recruiting = false; + private MovementState movementState = MovementState.IDLE; + private MovementState lastMovementState = MovementState.IDLE; + private int overFlowEXP = 0; + private int lastGuildToInvite; + private int lastGroupToInvite; + private boolean follow = false; + private HashSet loadedStaticObjects = new HashSet<>(); + private Vector3fImmutable lastStaticLoc = new Vector3fImmutable(0.0f, 0.0f, 0.0f); + private GameObjectType lastTargetType; + private int lastTargetID; + private int hidden = 0; // current rank of hide/sneak/invis + private int seeInvis = 0; // current rank of see invis + private float speedMod; + private boolean teleportMode = false; // Teleport on MoveToPoint + private float dexPenalty; + private long lastPlayerAttackTime = 0; + private long lastUpdateTime = System.currentTimeMillis(); + private long lastStamUpdateTime = System.currentTimeMillis(); + private boolean safeZone = false; + private int bindBuildingID; + + /* + DataWarehouse based kill/death tracking. + These sets contain the last 10 UUID's + */ + private boolean noTeleScreen = false; + private int lastRealmID = -2; + private int subRaceID = 0; + private boolean hasAnniversery = false; + private DeferredPowerJob weaponPower; + private NPC lastNPCDialog; + private Mob pet; + //Used for skill/Power calculation optimization + private CharacterTitle title = CharacterTitle.NONE; + private boolean asciiLastName = true; + private int spamCount = 0; + private boolean initialized = false; + private boolean enteredWorld = false; + private boolean canBreathe = true; + private String hash; + private ArrayList guildHistory = new ArrayList<>(); + private boolean wasTripped75 = false; + private boolean wasTripped50 = false; + private boolean wasTripped25 = false; + private float characterHeight = 0; + private boolean lastSwimming = false; + private boolean isTeleporting = false; + private boolean dirtyLoad = true; + private final ReadWriteLock dirtyLock = new ReentrantReadWriteLock(true); + + /** + * No Id Constructor + */ + public PlayerCharacter(String firstName, String lastName, short strMod, short dexMod, short conMod, short intMod, + short spiMod, Guild guild, byte runningTrains, Account account, Race race, BaseClass baseClass, byte skinColor, byte hairColor, + byte beardColor, byte hairStyle, byte beardStyle) { + super(firstName, lastName, (short) 1, (short) 1, (short) 1, (short) 1, (short) 1, (short) 1, 0, + Vector3fImmutable.ZERO, Vector3fImmutable.ZERO, + guild, runningTrains); + + this.runes = new ArrayList<>(); + this.account = account; + this.notDeleted = true; + this.race = race; + this.baseClass = baseClass; + this.skinColor = skinColor; + this.hairColor = hairColor; + this.beardColor = beardColor; + this.hairStyle = hairStyle; + this.beardStyle = beardStyle; + this.lfGroup = false; + this.lfGuild = false; + + this.strMod.set(strMod); + this.dexMod.set(dexMod); + this.conMod.set(conMod); + this.intMod.set(intMod); + this.spiMod.set(spiMod); + + this.guildStatus = new AtomicInteger(0); + this.bindBuildingID = -1; + } + + /** + * ResultSet Constructor + */ + public PlayerCharacter(ResultSet rs) throws SQLException { + super(rs, true); + + this.runes = DbManager.CharacterRuneQueries.GET_RUNES_FOR_CHARACTER(this.getObjectUUID()); + int accountID = rs.getInt("parent"); + this.account = DbManager.AccountQueries.GET_ACCOUNT(accountID); + this.gridObjectType = GridObjectType.DYNAMIC; + + this.notDeleted = rs.getBoolean("char_isActive"); + + int raceID = rs.getInt("char_raceID"); + this.race = Race.getRace(raceID); + + int baseClassID = rs.getInt("char_baseClassID"); + this.baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID); + + int promotionClassID = rs.getInt("char_promotionClassID"); + this.promotionClass = DbManager.PromotionQueries.GET_PROMOTION_CLASS(promotionClassID); + + this.skinColor = rs.getByte("char_skinColor"); + this.hairColor = rs.getByte("char_hairColor"); + this.beardColor = rs.getByte("char_beardColor"); + this.hairStyle = rs.getByte("char_hairStyle"); + this.beardStyle = rs.getByte("char_beardStyle"); + + this.lfGroup = false; + this.lfGuild = false; + + //TODO Unify game object with database after DB overhaul + this.guildStatus = new AtomicInteger(0); + + Guild guild = Guild.getGuild(this.getGuildUUID()); + if (guild != null && guild.isGuildLeader(this.getObjectUUID())) + this.setGuildLeader(true); + else + this.setGuildLeader(false); + + this.hasAnniversery = rs.getBoolean("anniversery"); + + this.setInnerCouncil(rs.getBoolean("guild_isInnerCouncil")); + this.setFullMember(rs.getBoolean("guild_isFullMember")); + this.setTaxCollector(rs.getBoolean("guild_isTaxCollector")); + this.setRecruiter(rs.getBoolean("guild_isRecruiter")); + this.setGuildTitle(rs.getInt("guild_title")); + + if (this.account != null) + this.ignoredPlayerIDs = DbManager.PlayerCharacterQueries.GET_IGNORE_LIST(this.account.getObjectUUID(), false); + + this.strMod.set(rs.getShort("char_strMod")); + this.dexMod.set(rs.getShort("char_dexMod")); + this.conMod.set(rs.getShort("char_conMod")); + this.intMod.set(rs.getShort("char_intMod")); + this.spiMod.set(rs.getShort("char_spiMod")); + + this.bindBuildingID = rs.getInt("char_bindBuilding"); + + this.hash = rs.getString("hash"); + + + // For debugging skills + // CharacterSkill.printSkills(this); + } + + public static Building getUpdatedBindBuilding(PlayerCharacter player) { + Building returnBuilding = null; + + //update bindBuilding based on Guild or nation TOL; + + if (player.getBindBuildingID() == 0) { + + returnBuilding = PlayerCharacter.getBindBuildingForGuild(player); + + if (returnBuilding != null) + player.setBindBuildingID(returnBuilding.getObjectUUID()); + return returnBuilding; + } + returnBuilding = BuildingManager.getBuildingFromCache(player.getBindBuildingID()); + + if (returnBuilding == null) { + returnBuilding = PlayerCharacter.getBindBuildingForGuild(player); + + if (returnBuilding != null) + player.setBindBuildingID(returnBuilding.getObjectUUID()); + } + return returnBuilding; + } + + public static Building getBindBuildingForGuild(PlayerCharacter player) { + + Building returnBuilding; + + if (player.getGuild() == null || player.getGuild().isEmptyGuild()) + return null; + + if (player.getGuild().getOwnedCity() == null) { + + if (player.getGuild().getNation().getOwnedCity() == null) + return null; + + if (player.getGuild().getNation().getOwnedCity().getTOL() == null) + return null; + + returnBuilding = player.getGuild().getNation().getOwnedCity().getTOL(); + player.setBindBuildingID(returnBuilding.getObjectUUID()); + return returnBuilding; + } + + if (player.getGuild().getOwnedCity().getTOL() == null) + return null; + + returnBuilding = player.getGuild().getOwnedCity().getTOL(); + return returnBuilding; + } + + public static void __serializeForClientMsg(PlayerCharacter playerCharacter, ByteBufferWriter writer) throws SerializationException { + serializeForClientCommon(playerCharacter, writer, true, false, false, false); + } + + public static void serializeForClientMsgLogin(PlayerCharacter playerCharacter, ByteBufferWriter writer) throws SerializationException { + serializeForClientCommon(playerCharacter, writer, true, false, false, false); + } + + public static void serializeForClientMsgCommit(PlayerCharacter playerCharacter, ByteBufferWriter writer) throws SerializationException { + serializeForClientCommon(playerCharacter, writer, true, true, false, false); + } + + public static void serializeForClientMsgFull(PlayerCharacter playerCharacter, ByteBufferWriter writer) throws SerializationException { + serializeForClientCommon(playerCharacter, writer, false, false, false, false); + } + + public static void serializeForClientMsgOtherPlayer(PlayerCharacter playerCharacter, ByteBufferWriter writer) throws SerializationException { + serializeForClientCommon(playerCharacter, writer, false, false, true, false); + } + + public static void serializePlayerForClientMsgOtherPlayer(PlayerCharacter playerCharacter, ByteBufferWriter writer, boolean hideAsciiLastName) throws SerializationException { + serializeForClientCommon(playerCharacter, writer, false, false, true, hideAsciiLastName); + } + + // TODO what is a Fresh Char? + private static void serializeForClientCommon(PlayerCharacter playerCharacter, ByteBufferWriter writer, boolean loginData, boolean freshChar, boolean otherPlayer, boolean hideAsciiLastName) + throws SerializationException { + + /* + * RUNES + */ + // Handle Applied Runes + writer.putInt(0); // Pad + writer.putInt(0); // Pad + + // Put number of runes + //We need to send all runes to everyone, otherwise playerCharacter will cause major issues + if (playerCharacter.promotionClass != null) + writer.putInt(playerCharacter.runes.size() + 3); + else + writer.putInt(playerCharacter.runes.size() + 2); + + // Cant forget that Race and baseClass are technically Runes :0 + if (playerCharacter.subRaceID != 0) { + writer.putInt(1); // For Race + writer.putInt(0); // Pad + writer.putInt(playerCharacter.subRaceID); + + writer.putInt(Enum.GameObjectType.Race.ordinal()); + writer.putInt(playerCharacter.subRaceID); + } else + playerCharacter.race.serializeForClientMsg(writer); + if (playerCharacter.promotionClass != null) { + BaseClass.serializeForClientMsg(playerCharacter.baseClass, writer, 2); + PromotionClass.serializeForClientMsg(playerCharacter.promotionClass, writer); + } else + BaseClass.serializeForClientMsg(playerCharacter.baseClass, writer, 3); + + // Put runes. + + for (CharacterRune rb : playerCharacter.runes) { + CharacterRune.serializeForClientMsg(rb, writer); + } + + /* + * STATS + */ + // Number of Stats to follow + writer.putInt(5); + + writer.putInt(MBServerStatics.STAT_STR_ID); // Strength ID + writer.putInt(freshChar ? 0 : playerCharacter.getStrMod()); + + writer.putInt(MBServerStatics.STAT_SPI_ID); // Spirit ID + writer.putInt(freshChar ? 0 : playerCharacter.getSpiMod()); + + writer.putInt(MBServerStatics.STAT_CON_ID); // Constitution ID + writer.putInt(freshChar ? 0 : playerCharacter.getConMod()); + + writer.putInt(MBServerStatics.STAT_DEX_ID); // Dexterity ID + writer.putInt(freshChar ? 0 : playerCharacter.getDexMod()); + + writer.putInt(MBServerStatics.STAT_INT_ID); // Intelligence ID + writer.putInt(freshChar ? 0 : playerCharacter.getIntMod()); + + // Handle Info + playerCharacter.title._serializeFirstName(writer, playerCharacter.firstName); + playerCharacter.title._serializeLastName(writer, playerCharacter.lastName, hideAsciiLastName, playerCharacter.asciiLastName); + + // Unknown + writer.putInt(0); + + writer.putString(ConfigManager.MB_WORLD_NAME.getValue()); + writer.putInt(WorldServer.worldMapID); + + writer.put((byte) 1); // End Datablock byte + writer.putInt(0); // Unsure, Pad? + writer.putInt(playerCharacter.getObjectType().ordinal()); + writer.putInt(playerCharacter.getObjectUUID()); + + // Perhaps playerCharacter is loc and the next 3 are Facing dir? + writer.putFloat(1); // Unknown + writer.putFloat(playerCharacter.race.getRaceType().getScaleHeight()); // Unknown + writer.putFloat(1); // Unknown + + writer.putVector3f(playerCharacter.getLoc()); + writer.putFloat(playerCharacter.faceDir.getRotation()); // Rotation, direction + + // facing + + // Running trains. + + if (otherPlayer) { + CharacterSkill runSkill = playerCharacter.skills.get("Running"); + if (runSkill == null) + // Logger.log.log( + // LogEventType.WARNING, + // "Failed to find the 'Running Skill' when serializing PlayerCharacter '" + // + playerCharacter.getCombinedName() + "'"); + // TODO put int=0 for now. + writer.putInt(0); + else + writer.putInt(runSkill.getNumTrains()); + } else + writer.putInt(0); + + + ArrayList equipped = playerCharacter.charItemManager.getEquippedList(); + + writer.putInt(equipped.size()); + for (Item item : equipped) { + Item._serializeForClientMsg(item, writer); + } + writer.putInt(playerCharacter.getRank()); + + writer.putInt(playerCharacter.getLevel()); + if (loginData) + writer.putInt(5); + else + writer.putInt(playerCharacter.getIsSittingAsInt()); // 5 + writer.putInt(playerCharacter.getIsWalkingAsInt()); // 1 + writer.putInt(playerCharacter.getIsCombatAsInt()); // 1 + writer.putInt(playerCharacter.getIsFlightAsInt()); // 2 or 3 + + writer.putInt(playerCharacter.getIsLfGroupAsInt()); // 1 + + // if (loginData) + // writer.putInt(0); + // else + writer.putInt(playerCharacter.getHeadlightsAsInt()); + + + if (playerCharacter.region != null && !loginData) { + Building building = Regions.GetBuildingForRegion(playerCharacter.region); + + if (building == null) { + writer.putInt(0); + writer.putInt(0); + } else { + writer.putInt(GameObjectType.Building.ordinal()); + writer.putInt(building.getObjectUUID()); + } + + } else { + writer.putInt(0); + writer.putInt(0); + } + + + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + writer.putInt(0); + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + +// writer.putInt(0); +// writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + if (!playerCharacter.isAlive() && otherPlayer) { + writer.putInt(0); + writer.putInt(0); + } + + + //TODO FIGURE OUT THE REAL SEARLIZATION FOR NEXT 2 SHORTS? + writer.putInt(playerCharacter.skinColor); // Skin Color + writer.putFloat(20); + writer.put((byte) 0); //Unknown + + //unknown object + writer.putInt(0); + writer.putInt(0); + + //unknown type + writer.putInt(0); + //0x4080 should be the next short here, instead it wraps 0's down their in for loops.. seriously.. who wrote playerCharacter shit. + // playerCharacter aint right! + // ByteBufferUtils.putString(writer, playerCharacter.guild.getName()); + // writer.putInt(playerCharacter.getGuild().getUUID()); + // ByteBufferUtils.putString(writer, playerCharacter.guild.getNation().getName()); + // writer.putInt(playerCharacter.getGuild().getNation().getUUID()); + Guild.serializeForClientMsg(playerCharacter.getGuild(), writer, playerCharacter, false); + + //Send Tokens for race/class/promotion (disciplines?) + if (playerCharacter.promotionClass != null) + writer.putInt(3); + else + writer.putInt(2); + writer.putInt(playerCharacter.race.getToken()); + writer.putInt(playerCharacter.baseClass.getToken()); + if (playerCharacter.promotionClass != null) + writer.putInt(playerCharacter.promotionClass.getToken()); + // writer.putInt(2); // Unknown Counter + // writer.putInt(0x04C1BE88); // Unknown + // writer.putInt(0x0F651512); // Unknown + + writer.putFloat(playerCharacter.altitude); // altitude? + writer.putFloat(playerCharacter.altitude); // altitude? + writer.put((byte) 0); // End Datablock byte + + writer.putFloat(playerCharacter.healthMax); + writer.putFloat(playerCharacter.health.get()); + + writer.put((byte) 0); // End Datablock byte + //size + + + if (loginData) { + writer.putInt(0); + } else { + int indexPosition = writer.position(); + writer.putInt(0); //placeholder for item cnt + int total = 0; + // Logger.info("",""+ playerCharacter.getEffects().size()); + for (Effect eff : playerCharacter.getEffects().values()) { + if (eff.getPower() == null && otherPlayer) + continue; + if (eff.getPower() != null && eff.getPower().token == 429506619) // Oblivion's Caress + continue; + if (!eff.serializeForLoad(writer)) + continue; + ++total; + + } + + writer.putIntAt(total, indexPosition); + } + + if (otherPlayer) { + writer.put((byte) 0); // End Datablock Byte + return; + + } + + //made up for sendalleffects + //writer.putInt(0); // Pad + //writer.put((byte) 0); // End Datablock byte + writer.putInt(playerCharacter.getUnusedStatPoints()); + writer.putInt(playerCharacter.getLevel()); + writer.putInt(playerCharacter.getExp() + playerCharacter.overFlowEXP); + writer.putFloat(playerCharacter.manaMax); + writer.putFloat(playerCharacter.mana.get()); + writer.putFloat(playerCharacter.staminaMax); + writer.putFloat(playerCharacter.stamina.get()); + writer.putInt(playerCharacter.getAtrHandOne()); + writer.putInt(playerCharacter.getAtrHandTwo()); + writer.putInt(playerCharacter.getDefenseRating()); + + if (MBServerStatics.POWERS_DEBUG) //debug mode, grant lots of trains + writer.putInt(1000); + else + writer.putInt(playerCharacter.trainsAvailable.get()); + + /* + * Skills + */ + if (loginData) + writer.putInt(0); // Skip skills + else { + writer.putInt(playerCharacter.skills.size()); + Iterator it = playerCharacter.skills.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + CharacterSkill.serializeForClientMsg(playerCharacter.skills.get(name), writer); + } + } + + /* + * Powers + */ + if (loginData) + writer.putInt(0); // Skip Powers + else if (MBServerStatics.POWERS_DEBUG) //debug mode, grant all powers + PowersManager.testPowers(writer); + else { + writer.putInt(playerCharacter.powers.size()); + for (CharacterPower sp : playerCharacter.powers.values()) { + CharacterPower.serializeForClientMsg(sp, writer); + } + } + + /* + * Inventory + */ + if (loginData) { + writer.putInt(0); // Skip Inventory + writer.putInt(playerCharacter.getInventoryCapacity()); // Inventory Capacity + + } else { + ArrayList inv = playerCharacter.charItemManager.getInventory(true); + Item.putList(writer, inv, false, playerCharacter.getObjectUUID()); + writer.putInt(playerCharacter.getInventoryCapacityRemaining()); + } + + /* + * Bank + */ + if (loginData) { + writer.putInt(0); // Skip Bank + writer.putInt(AbstractCharacter.getBankCapacity()); // Bank Capacity + + } else { + ArrayList bank = playerCharacter.charItemManager.getBank(); + + Item.putList(writer, bank, false, playerCharacter.getObjectUUID()); + writer.putInt(playerCharacter.getBankCapacityRemaining()); + } + //load player friends. + if (loginData) + writer.putInt(0); + else { + HashSet friendMap = PlayerFriends.PlayerFriendsMap.get(playerCharacter.getObjectUUID()); + if (friendMap == null) + writer.putInt(0); + else { + writer.putInt(friendMap.size()); + for (int friendID : friendMap) { + PlayerCharacter friend = PlayerCharacter.getFromCache(friendID); + //shouldn't get here, but if null serialize blank friend. + if (friend == null) { + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + } else { + writer.putInt(friend.getObjectType().ordinal()); + writer.putInt(friend.getObjectUUID()); + writer.putString(friend.getName()); + boolean online = SessionManager.getPlayerCharacterByID(friend.getObjectUUID()) != null ? true : false; + writer.putInt(online ? 0 : 1); + writer.putInt(friend.friendStatus.ordinal()); + } + + } + } + } + + + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + writer.putShort((short) 0); + writer.put((byte) 0); + // playerCharacter is for send self in enter world (full character) + if (!loginData && !freshChar) { + int size = playerCharacter.getRecycleTimers().size(); + writer.putInt(size); + if (size > 0) + for (int token : playerCharacter.getRecycleTimers().keySet()) { + + JobContainer frtj = playerCharacter.getRecycleTimers().get(token); + long timeLeft = frtj.timeOfExection() - System.currentTimeMillis(); + writer.putInt(token); + writer.putInt((int) timeLeft / 1000); + } + DateTime enterWorld = new DateTime(playerCharacter.timestamps.get("EnterWorld")); + writer.putDateTime(enterWorld); + + writer.putInt(0x49EF1E98); //DUnno what playerCharacter is. + writer.putFloat(DateTime.now().hourOfDay().get()); //daylight in float. + writer.putFloat(6); //interval of light to change per game hour //float + //writer.putInt(1637194901); //playerCharacter is actually an opcode taht is in recordings, no clue what it is, dumped it and it changes nothing + } else { + writer.put((byte) 0); //added to compensate the cooldown check. + + //add server up or down + int serverUp = LoginServer.worldServerRunning ? 1 : 0; + + if (playerCharacter.account == null) + serverUp = 0; + + if ((playerCharacter.account.status.equals(AccountStatus.ADMIN) == false) && + (playerCharacter.account.status.equals(WorldServer.worldAccessLevel) == false)) + serverUp = 0; + + writer.putInt(serverUp); + writer.putInt(0); // effects, not sure used by players + writer.put((byte) 0); // End Player Datablock + } + + } + + public static PlayerCharacter generatePCFromCommitNewCharacterMsg(Account a, CommitNewCharacterMsg msg, ClientConnection clientConnection) { + + String firstName = msg.getFirstName().trim(); + String lastName = msg.getLastName().trim(); + + if (firstName.length() < 3) { + LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTNAME_MUST_BE_LONGER, + clientConnection); + return null; + } + + // Ensure names are below required length + if (firstName.length() > 15 || lastName.length() > 15) { + LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTANDLAST_MUST_BE_SHORTER, + clientConnection); + return null; + } + + // Check if firstname is valid + if (MiscUtils.checkIfFirstNameInvalid(firstName)) { + LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_PLEASE_CHOOSE_ANOTHER_FIRSTNAME, + clientConnection); + return null; + } + + // Check if last name is valid + if (MiscUtils.checkIfLastNameInvalid(lastName)) { + LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_LASTNAME_UNAVAILABLE, + clientConnection); + return null; + } + + // Verify Race + int raceID = msg.getRace(); + + Race race = Race.getRace(raceID); + + if (race == null) { + Logger.info("Invalid RaceID: " + raceID); + return null; + } + + // Verify BaseClass Object. + int baseClassID = msg.getBaseClass(); + BaseClass baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID); + + if (baseClass == null) { + Logger.info("Invalid BaseClasID: " + baseClassID); + return null; + } + + // Verify Race/baseClass combo. + boolean valid = false; + + for (BaseClass bc : race.getValidBaseClasses()) { + + if (bc.getObjectUUID() == baseClassID) { + valid = true; + break; + } + } + + if (!valid) { + Logger.info("Invalid BaseClass/Race Combo"); + return null; + } + + // Verify HairStyle/BeardStyle/SkinColor/HairColor/BeardColor + int hairStyleID = msg.getHairStyle(); + int beardStyleID = msg.getBeardStyle(); + int skinColorID = msg.getSkinColor(); + int hairColorID = msg.getHairColor(); + int beardColorID = msg.getBeardColor(); + + if (!race.isValidHairStyle(hairStyleID)) { + Logger.info("Invalid HairStyleID: " + hairStyleID + " for race: " + race.getName()); + return null; + } + + if (!race.isValidSkinColor(skinColorID)) { + Logger.info("Invalid skinColorID: " + skinColorID + " for race: " + race.getName()); + return null; + } + + if (!race.isValidHairColor(hairColorID)) { + Logger.info("Invalid hairColorID: " + hairColorID + " for race: " + race.getName()); + return null; + } + + if (!race.isValidBeardColor(beardColorID)) { + Logger.info("Invalid beardColorID: " + beardColorID + " for race: " + race.getName()); + return null; + } + + // Get stat modifiers + int strMod = msg.getStrengthMod(); + int dexMod = msg.getDexterityMod(); + int conMod = msg.getConstitutionMod(); + int intMod = msg.getIntelligenceMod(); + int spiMod = msg.getSpiritMod(); + + + if (intMod < -5 || dexMod < -5 || conMod < -5 || strMod < -5 || spiMod < -5) { + Logger.error("NEGATIVE STAT CHEAT ATTEMPTED! ACCOUNT: " + a.getUname() + "(" + a.getObjectUUID() + ") IP ADDRESS: " + clientConnection.getClientIpAddress()); + return null; + } + + // calculate current stats: + short strCur = (short) (race.getStrStart() + baseClass.getStrMod() + strMod); + short dexCur = (short) (race.getDexStart() + baseClass.getDexMod() + dexMod); + short conCur = (short) (race.getConStart() + baseClass.getConMod() + conMod); + short intCur = (short) (race.getIntStart() + baseClass.getIntMod() + intMod); + short spiCur = (short) (race.getSpiStart() + baseClass.getSpiMod() + spiMod); + + // calculate max stats: + short strMax = race.getStrMax(); + short dexMax = race.getDexMax(); + short conMax = race.getConMax(); + short intMax = race.getIntMax(); + short spiMax = race.getSpiMax(); + + // Verify not too many runes applied + int numRunes = msg.getNumRunes(); + + if (numRunes > 16) { + Logger.info("Too many Runes applied"); + return null; + } + + // Get Runes + // ArrayList characterRunesUsed = new ArrayList(); + // ArrayList subtypesUsed = new ArrayList(); + int remainingPoints = race.getStartingPoints() - strMod - dexMod - conMod - intMod - spiMod; + + int[] characterRunes = msg.getRunes(); + + HashSet usedRunesSubType = new HashSet<>(); + HashSet usedRunes = new HashSet<>(); + + // So that all the penalties can be added at the end. + ConcurrentHashMap penalties = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + penalties.put("StrCur", 0); + penalties.put("StrMax", 0); + penalties.put("DexCur", 0); + penalties.put("DexMax", 0); + penalties.put("ConCur", 0); + penalties.put("ConMax", 0); + penalties.put("IntCur", 0); + penalties.put("IntMax", 0); + penalties.put("SpiCur", 0); + penalties.put("SpiMax", 0); + + PriorityQueue> orderedRunes = new PriorityQueue<>(14, + new Comparator>() { + + @Override + public int compare(Entry o1, Entry o2) { + return o1.getKey() - o2.getKey(); + } + }); + + // Figure out which Runes we are adding. + for (int i : characterRunes) { + // Zero skip + if (i == 0) + continue; + + // Skip the Race and BaseClass runes... already dealt with. + if (i == raceID || i == baseClassID) + continue; + + RuneBase runeBase = RuneBase.getRuneBase(i); + + // Null check + if (runeBase == null) { + Logger.info("GOM returned NULL RuneBase"); + return null; + } + + // Validate Rune against Race + if (!race.isAllowedRune(runeBase)) { + Logger.info("Trait Not valid for Race"); + return null; + } + + // Validate BaseClass against Race + if (!baseClass.isAllowedRune(runeBase)) { + Logger.info("Trait Not valid for BaseClass"); + return null; + } + + int previous_size = usedRunes.size(); + int previous_subtype = usedRunesSubType.size(); + + usedRunes.add(runeBase); + usedRunesSubType.add(runeBase.getSubtype()); + + // Duplicate Rune check + if (usedRunes.size() <= previous_size) { + Logger.info("Duplicate RuneBase"); + return null; + } + + // Duplicate Subtype check + if (runeBase.getSubtype() != 0 && usedRunesSubType.size() <= previous_subtype) { + Logger.info("Duplicate RuneBase Subtype"); + return null; + } + + int maxValue = 0; + + // Every attempt is made to load MIN_NEEDED_ATTRIBUTES first. + + if (runeBase.getAttrs() != null) + for (RuneBaseAttribute rba : runeBase.getAttrs()) { + if (rba.getAttributeID() == MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID + || rba.getAttributeID() == MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID + || rba.getAttributeID() == MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID + || rba.getAttributeID() == MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID + || rba.getAttributeID() == MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID) { + maxValue = rba.getModValue(); + if (runeBase.getName().equals("Giant's Blood")) + maxValue = 45; // Take care of the Giant's Blood special + // case. + break; + } + } + + orderedRunes.add(new AbstractMap.SimpleEntry<>(maxValue, runeBase)); + } + + while (orderedRunes.size() > 0) { + RuneBase rb = orderedRunes.remove().getValue(); + ArrayList attrs = rb.getAttrs(); + + if (attrs != null) + for (RuneBaseAttribute abr : attrs) { + + int attrID = abr.getAttributeID(); + int value = abr.getModValue(); + + switch (attrID) { + case MBServerStatics.RUNE_COST_ATTRIBUTE_ID: + + Logger.info("Bought " + rb.getName() + " for " + value + " points. " + + (remainingPoints - value) + " left."); + + if ((remainingPoints - value) >= 0) { + remainingPoints -= value; + continue; + } + Logger.info("Not enough points left"); + return null; + case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID: + + if (strCur >= value) + continue; + + Logger.info("STR fails to meet Rune Minimum --> " + rb.getName()); + return null; + case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID: + + if (dexCur >= value) + continue; + + Logger.info("DEX fails to meet Rune Minimum --> " + rb.getName()); + return null; + case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID: + + if (conCur >= value) + continue; + + Logger.info("CON fails to meet Rune Minimum --> " + rb.getName()); + return null; + case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID: + + if (intCur >= value) + continue; + + Logger.info("INT fails to meet Rune Minimum --> " + rb.getName()); + return null; + case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID: + + if (spiCur >= value) + continue; + + Logger.info("SPI fails to meet Rune Minimum --> " + rb.getName()); + return null; + case MBServerStatics.RUNE_STR_ATTRIBUTE_ID: + + if (value < 0) + penalties.put("StrCur", (penalties.get("StrCur") + value)); + else + strCur += value; + continue; + + case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID: + if (value < 0) + penalties.put("DexCur", (penalties.get("DexCur") + value)); + else + dexCur += value; + continue; + case MBServerStatics.RUNE_CON_ATTRIBUTE_ID: + if (value < 0) + penalties.put("ConCur", (penalties.get("ConCur") + value)); + else + conCur += value; + continue; + case MBServerStatics.RUNE_INT_ATTRIBUTE_ID: + if (value < 0) + penalties.put("IntCur", (penalties.get("IntCur") + value)); + else + intCur += value; + continue; + case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID: + if (value < 0) + penalties.put("SpiCur", (penalties.get("SpiCur") + value)); + else + spiCur += value; + continue; + case MBServerStatics.RUNE_STR_MAX_ATTRIBUTE_ID: + if (value < 0) + penalties.put("StrMax", (penalties.get("StrMax") + value)); + else + strMax += value; + continue; + case MBServerStatics.RUNE_DEX_MAX_ATTRIBUTE_ID: + if (value < 0) + penalties.put("DexMax", (penalties.get("DexMax") + value)); + else + dexMax += value; + continue; + case MBServerStatics.RUNE_CON_MAX_ATTRIBUTE_ID: + if (value < 0) + penalties.put("ConMax", (penalties.get("ConMax") + value)); + else + conMax += value; + continue; + case MBServerStatics.RUNE_INT_MAX_ATTRIBUTE_ID: + if (value < 0) + penalties.put("IntMax", (penalties.get("IntMax") + value)); + else + intMax += value; + continue; + case MBServerStatics.RUNE_SPI_MAX_ATTRIBUTE_ID: + if (value < 0) + penalties.put("SpiMax", (penalties.get("SpiMax") + value)); + else + spiMax += value; + continue; + + default: + Logger.info("Unknown ATTRIBUTE_ID while checking RuneBaseAttributes: " + attrID); + return null; + } + } + } + + // Add in all of the penalties. + strCur += penalties.get("StrCur"); + strMax += penalties.get("StrMax"); + dexCur += penalties.get("DexCur"); + dexMax += penalties.get("DexMax"); + conCur += penalties.get("ConCur"); + conMax += penalties.get("ConMax"); + intCur += penalties.get("IntCur"); + intMax += penalties.get("IntMax"); + spiCur += penalties.get("SpiCur"); + spiMax += penalties.get("SpiMax"); + + int kitID = msg.getKit(); + + // get the correctKit + int raceClassID = Kit.GetKitIDByRaceClass(raceID, baseClassID); + ArrayList allKits = Kit.RaceClassIDMap.get(raceClassID); + + Kit kit = null; + + for (Kit k : allKits) { + if (k.getKitNumber() == kitID) { + kit = k; + break; + } + } + + if (kit == null) { + Logger.info("Unable to find matching kitID: " + kitID); + return null; + } + + byte runningTrains = 0; + PlayerCharacter playerCharacter; + + //Synchronized block to allow exclusive access when confirming + //uniqueness of FirstName and subsequently saving the new record + //to the database with that FirstName + synchronized (FirstNameLock) { + // Test if FirstName already exists. + // This must be the very last check before calling the + // DB to create the character record + if (DbManager.PlayerCharacterQueries.IS_CHARACTER_NAME_UNIQUE(firstName) == false) { + LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTNAME_UNAVAILABLE, + clientConnection); + return null; + } + + // Make PC + PlayerCharacter pcWithoutID = new PlayerCharacter(firstName, lastName, (short) strMod, (short) dexMod, (short) conMod, + (short) intMod, (short) spiMod, Guild.getErrantGuild(), runningTrains, a, race, baseClass, (byte) skinColorID, (byte) hairColorID, + (byte) beardColorID, (byte) beardStyleID, (byte) hairStyleID); + + try { + playerCharacter = DbManager.PlayerCharacterQueries.ADD_PLAYER_CHARACTER(pcWithoutID); + } catch (Exception e) { + Logger.error("generatePCFromCommitNewCharacterMsg", "An error occurred while saving new PlayerCharacter to DB", e); + return null; + } + + if (playerCharacter == null) { + Logger.info("GOM Failed to create PlayerCharacter"); + return null; + } + + } // END synchronized(FirstNameLock) + + // Add creation runes + for (RuneBase rb : usedRunes) { + CharacterRune runeWithoutID = new CharacterRune(rb, playerCharacter.getObjectUUID()); + CharacterRune characterRune; + try { + characterRune = DbManager.CharacterRuneQueries.ADD_CHARACTER_RUNE(runeWithoutID); + } catch (Exception e) { + characterRune = null; + } + + if (characterRune == null) { + playerCharacter.deactivateCharacter(); + Logger.info("GOM Failed to create CharacterRune"); + return null; + } + + playerCharacter.addRune(characterRune); + } + + if (hairStyleID != 0) { + // Create Hair + Item tempHair = new Item(ItemBase.getItemBase(hairStyleID), playerCharacter.getObjectUUID(), OwnerType.PlayerCharacter, + (byte) 0, (byte) 0, (short) 1, (short) 1, false, false, ItemContainerType.EQUIPPED, + (byte) MBServerStatics.SLOT_HAIRSTYLE, new ArrayList<>(), ""); + + Item hair; + + try { + hair = DbManager.ItemQueries.ADD_ITEM(tempHair); + } catch (Exception e) { + hair = null; + } + + if (hair == null) { + playerCharacter.deactivateCharacter(); + Logger.info("GameObjectManager failed to create Hair:" + hairStyleID + " in Slot:" + + MBServerStatics.SLOT_HAIRSTYLE); + return null; + } + } + + if (beardStyleID != 0) { + // Create Beard + Item tempBeard = new Item(ItemBase.getItemBase(beardStyleID), playerCharacter.getObjectUUID(), OwnerType.PlayerCharacter, + (byte) 0, (byte) 0, (short) 1, (short) 1, false, false, ItemContainerType.EQUIPPED, + (byte) MBServerStatics.SLOT_BEARDSTYLE, new ArrayList<>(), ""); + Item beard; + try { + beard = DbManager.ItemQueries.ADD_ITEM(tempBeard); + } catch (Exception e) { + beard = null; + } + + if (beard == null) { + playerCharacter.deactivateCharacter(); + Logger.info("GameObjectManager failed to create Beard:" + beardStyleID + " in Slot:" + + MBServerStatics.SLOT_BEARDSTYLE); + return null; + } + } + // Create items from Kit and equip on character. + try { + kit.equipPCwithKit(playerCharacter); + } catch (Exception e) { + Logger.info("Unable to find KIT ID for Race: " + raceID + "||" + "Class:" + baseClassID); + playerCharacter.deactivateCharacter(); + return null; + } + + // Get any new skills that belong to the player + playerCharacter.calculateSkills(); + + a.setLastCharacter(playerCharacter.getObjectUUID()); + playerCharacter.charItemManager.load(); + + playerCharacter.activateCharacter(); + + return playerCharacter; + } + + public static boolean isIgnoreListFull() { + return false; //Why were we setting a limit on ignores? - + //return (ignoredPlayerIDs.size() >= MBServerStatics.IGNORE_LIST_MAX); + } + + public static boolean isIgnorable() { + return true; + // // if (account == null) return false; + // if (account.getAccessLevel() > 0) { + // return false; + // } + // return true; + } + + /** + * @ Initialize player upon creation + */ + public static void initializePlayer(PlayerCharacter player) { + + if (player.initialized) + return; + // Logger.info("", " Initializing " + player.getCombinedName()); + player.skills = DbManager.CharacterSkillQueries.GET_SKILLS_FOR_CHARACTER(player); + player.powers = player.initializePowers(); + + + if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)) + player.setLoc(player.bindLoc); + player.endLoc = Vector3fImmutable.ZERO; + + //get level based on experience + player.level = (short) Experience.getLevel(player.exp); + + player.setHealth(999999f); + player.mana.set(999999f); + player.stamina.set(999999f); + player.bonuses = new PlayerBonuses(player); + PlayerBonuses.InitializeBonuses(player); + player.resists = new Resists(player); + player.charItemManager.load(); + + if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)) { + + //CharacterSkill.updateAllBaseAmounts(this); + CharacterPower.grantTrains(player); + + // calculate skills. Make sure none are missing. + AbstractCharacter.runBonusesOnLoad(player); + + PlayerCharacter.InitializeSkillsOnLoad(player); + + //apply all bonuses + player.recalculatePlayerStats(true); + player.trainsAvailable.set(CharacterSkill.getTrainsAvailable(player)); + + if (player.trainsAvailable.get() < 0) + player.recalculateTrains(); + + //this.resists.calculateResists(this); + player.newChar = true; + + //check current guild valid for player + player.checkGuildStatus(); + + player.setHealth(player.getHealthMax()); + player.mana.set(player.manaMax); + player.stamina.set(player.staminaMax); + } else + player.setBindLoc(Vector3fImmutable.ZERO); + + player.initialized = true; + + String lastAscii = player.lastName.replaceAll("[^\\p{ASCII}]", ""); + player.asciiLastName = lastAscii.equals(player.lastName); + } + + public static void recalculatePlayerStatsOnLoad(PlayerCharacter pc) { + + //calculate base stats + pc.calculateBaseStats(); + + //calculate base skills + CharacterSkill.updateAllBaseAmounts(pc); + pc.calculateModifiedStats(); + + //calculate modified skills + CharacterSkill.updateAllModifiedAmounts(pc); + + + //calculate modified stats + + + //calculate ATR, damage and defense + pc.calculateAtrDefenseDamage(); + + //calculate movement bonus + pc.calculateSpeedMod(); + + // recalculate Max Health/Mana/Stamina + pc.calculateMaxHealthManaStamina(); + + // recalculate Resists + Resists.calculateResists(pc); + + } + + public static boolean hideNonAscii() { + + return false; + } + + public static float getDexPenalty(Item armor) { + if (armor == null) + return 0f; + ItemBase ab = armor.getItemBase(); + if (ab == null) + return 0f; + return ab.getDexPenalty(); + } + + private static float getModifiedAmount(CharacterSkill skill) { + if (skill == null) + return 0f; + return skill.getModifiedAmount(); + } + + public static void InitializeSkillsOnLoad(PlayerCharacter pc) { + try { + { + + //see if any new skills or powers granted + CharacterSkill.calculateSkills(pc); + + // calculate granted Trains in powers. + CharacterPower.grantTrains(pc); + + //see if any new powers unlocked from previous check + CharacterPower.calculatePowers(pc); + } + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + } + + public static String getFirstName(int tableId) { + + PlayerCharacter player; + + if (tableId == 0) + return ""; + + player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, tableId); + + return player.getFirstName(); + } + + public static PlayerCharacter getFromCache(int id) { + return (PlayerCharacter) DbManager.getFromCache(GameObjectType.PlayerCharacter, id); + } + + public static PlayerCharacter getByFirstName(String name) { + + PlayerCharacter returnPlayer = null; + for (AbstractGameObject ago : DbManager.getList(GameObjectType.PlayerCharacter)) { + PlayerCharacter cachePlayer = (PlayerCharacter) ago; + if (!name.equalsIgnoreCase(cachePlayer.getFirstName())) + continue; + if (cachePlayer.isDeleted()) + continue; + returnPlayer = cachePlayer; + break; + } + + return returnPlayer; + } + + public static PlayerCharacter getPlayerCharacter(int uuid) { + + PlayerCharacter outPlayer; + + outPlayer = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(uuid); + + if (outPlayer != null) + return outPlayer; + + return (PlayerCharacter) DbManager.getFromCache(GameObjectType.PlayerCharacter, uuid); + } + + public static int GetPlayerRealmTitle(PlayerCharacter player) { + + if (player.getGuild().isEmptyGuild()) + return 0; + if (!player.getGuild().isGuildLeader(player.getObjectUUID())) + return 0; + if (player.getGuild().getOwnedCity() == null) + return 10; + if (player.getGuild().getOwnedCity().getRealm() == null) + return 10; + if (player.getGuild().getOwnedCity().getRealm().getRulingCity() == null) + return 10; + + if (player.getGuild().getOwnedCity().getRealm().getRulingCity().getObjectUUID() != player.getGuild().getOwnedCity().getObjectUUID()) + return 10; + int realmTitle = 1; + if (player.getGuild().getSubGuildList() == null || player.getGuild().getSubGuildList().isEmpty()) + return 11; + for (Guild subGuild : player.getGuild().getSubGuildList()) { + if (subGuild.getOwnedCity() == null) + continue; + if (subGuild.getOwnedCity().getRealm() == null) + continue; + if (subGuild.getOwnedCity().getRealm().getRulingCity() == null) + continue; + if (subGuild.getOwnedCity().getRealm().getRulingCity().getObjectUUID() != subGuild.getOwnedCity().getObjectUUID()) + continue; + realmTitle++; + } + + if (realmTitle < 3) + return 11; + else if (realmTitle < 5) + return 12; + else + return 13; + } + + public static void UpdateClientPlayerRank(PlayerCharacter pc) { + if (pc == null) + return; + boolean disable = true; + + if (disable) + return; + UpdateCharOrMobMessage ucm = new UpdateCharOrMobMessage(pc, 2, pc.getRank()); + DispatchMessage.sendToAllInRange(pc, ucm); + } + + public static void GroundPlayer(PlayerCharacter groundee) { + if (groundee.getDesiredAltitude() == 0 && groundee.getAltitude() == 0) + return; + groundee.setAltitude(groundee.getAltitude()); + groundee.setDesiredAltitude(0); + groundee.setTakeOffTime(System.currentTimeMillis()); + + ChangeAltitudeMsg msg = ChangeAltitudeMsg.GroundPlayerMsg(groundee); + // force a landing + DispatchMessage.dispatchMsgToInterestArea(groundee, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + } + + public static boolean CanBreathe(PlayerCharacter breather) { + try { + if (breather.isFlying()) + return true; + Zone zone = ZoneManager.findSmallestZone(breather.getLoc()); + + if (zone.getSeaLevel() != 0) { + + float localAltitude = breather.getLoc().y; + + + if (localAltitude + breather.characterHeight < zone.getSeaLevel() - 2) + return false; + + if (breather.isMoving()) { + if (localAltitude + breather.characterHeight < zone.getSeaLevel()) + return false; + } + } else { + if (breather.getLoc().y + breather.characterHeight < -2) + return false; + + if (breather.isMoving()) { + if (breather.getLoc().y + breather.characterHeight < 0) + return false; + } + } + + + } catch (Exception e) { + Logger.info(breather.getName() + e); + } + + + return true; + } + + public static boolean enterWater(PlayerCharacter enterer) { + + try { + if (enterer.isFlying()) + return false; + + + Zone zone = ZoneManager.findSmallestZone(enterer.getLoc()); + + if (zone.getSeaLevel() != 0) { + + float localAltitude = enterer.getLoc().y + enterer.characterHeight; + + + if (localAltitude < zone.getSeaLevel()) + return true; + } else { + if (enterer.getLoc().y + enterer.characterHeight < 0) + return true; + } + } catch (Exception e) { + Logger.info(enterer.getName() + e); + } + + return false; + + } + + public static boolean LeaveWater(PlayerCharacter leaver) { + + try { + + + Zone zone = ZoneManager.findSmallestZone(leaver.getLoc()); + + float leaveWater = leaver.centerHeight; + + if (leaver.isMoving()) + leaveWater = 1f; + + + if (zone.getSeaLevel() != 0) { + + float localAltitude = leaver.getLoc().y; + + + if (localAltitude + leaveWater < zone.getSeaLevel()) + return false; + } else { + if (leaver.getLoc().y + leaveWater < 0) + return false; + } + } catch (Exception e) { + Logger.info(leaver.getName() + e); + } + + return true; + } + + public static boolean CanBindToBuilding(PlayerCharacter player, int buildingID) { + if (buildingID == 0) + return false; + + Building bindBuilding = BuildingManager.getBuildingFromCache(buildingID); + + if (bindBuilding == null) + return false; + + if (!BuildingManager.playerCanManage(player, bindBuilding)) + return false; + + return true; + } + + public void setGuildTitle(int value) { + if (GuildStatusController.getTitle(this.guildStatus) == value) + return; + DbManager.PlayerCharacterQueries.SET_GUILD_TITLE(this, value); + GuildStatusController.setTitle(guildStatus, value); + } + + public void setFullMember(boolean value) { + if (GuildStatusController.isFullMember(this.guildStatus) == value) + return; + DbManager.PlayerCharacterQueries.SET_FULL_MEMBER(this, value); + GuildStatusController.setFullMember(guildStatus, value); + } + + public void setRecruiter(boolean value) { + if (GuildStatusController.isRecruiter(this.guildStatus) == value) + return; + DbManager.PlayerCharacterQueries.SET_RECRUITER(this, value); + GuildStatusController.setRecruiter(guildStatus, value); + } + + public void setTaxCollector(boolean value) { + if (GuildStatusController.isTaxCollector(this.guildStatus) == value) + return; + DbManager.PlayerCharacterQueries.SET_TAX_COLLECTOR(this, value); + GuildStatusController.setTaxCollector(guildStatus, value); + } + + public void setInnerCouncil(boolean value) { + + // dont update if its the same. + if (GuildStatusController.isInnerCouncil(this.guildStatus) == value) + return; + + DbManager.PlayerCharacterQueries.SET_INNERCOUNCIL(this, value); + GuildStatusController.setInnerCouncil(guildStatus, value); + } + + public void setGuildLeader(boolean value) { + if (GuildStatusController.isGuildLeader(this.guildStatus) == value) + return; + + GuildStatusController.setGuildLeader(guildStatus, value); + if (value == true) { + this.setInnerCouncil(true); + this.setFullMember(true); + } + } + + //END -> Guild Status Interface + public void resetGuildStatuses() { + this.setInnerCouncil(false); + this.setFullMember(false); + this.setGuildTitle(0); + this.setTaxCollector(false); + this.setRecruiter(false); + this.setGuildLeader(false); + } + + /* + * Getters + */ + public byte getHairStyle() { + return hairStyle; + } + + public byte getBeardStyle() { + return beardStyle; + } + + public DeferredPowerJob getWeaponPower() { + return this.weaponPower; + } + + public void setWeaponPower(DeferredPowerJob value) { + this.weaponPower = value; + } + + public void setSafeZone(boolean value) { + this.safeZone = value; + } + + public boolean inSafeZone() { + return this.safeZone; + } + + public boolean isInSafeZone() { + + Zone zone = ZoneManager.findSmallestZone(this.getLoc()); + + if (zone != null) { + return zone.getSafeZone() == (byte) 1; + } + + return false; + //return this.safeZone; + } + + /** + * @return the account + */ + public Account getAccount() { + return account; + } + + public void deactivateCharacter() { + this.notDeleted = false; + DbManager.PlayerCharacterQueries.SET_DELETED(this); + DbManager.removeFromCache(this); + } + + public void activateCharacter() { + this.notDeleted = true; + DbManager.PlayerCharacterQueries.SET_DELETED(this); + } + + public boolean isDeleted() { + return !this.notDeleted; + } + + public ArrayList getRunes() { + return this.runes; + } + + public CharacterRune getRune(int runeID) { + if (this.runes == null) + return null; + for (CharacterRune cr : this.runes) { + if (cr.getRuneBase() != null && cr.getRuneBase().getObjectUUID() == runeID) + return cr; + } + return null; + } + + public boolean addRune(CharacterRune value) { + if (this.runes.size() > 12) // Max Runes + return false; + if (this.runes.indexOf(value) != -1) // Already contains rune + return false; + this.runes.add(value); + return true; + } + + public boolean removeRune(CharacterRune value) { + int index = this.runes.indexOf(value); + if (index == -1) + return false; + this.runes.remove(index); + return true; + } + + public CharacterRune removeRune(int runeID) { + Iterator it = this.runes.iterator(); + while (it.hasNext()) { + CharacterRune cr = it.next(); + if (cr != null) { + RuneBase rb = cr.getRuneBase(); + if (rb != null) + if (runeID == rb.getObjectUUID()) { + it.remove(); + DbManager.CharacterRuneQueries.DELETE_CHARACTER_RUNE(cr); + return cr; + } + } + } + return null; + } + + /** + * @ Kill this Character + */ + @Override + public void killCharacter(AbstractCharacter attacker) { + + killCleanup(); + + // *** Mobs have a separate combat path? Crazy shit! + // *** Mobs don't get Experience for killing players. everything else is done in killCleanup(); + + if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter) == false) { + + Zone zone = ZoneManager.findSmallestZone(this.getLoc()); + + //DeathShroud + + if (zone.getSafeZone() == 0) + PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false); + + //enable this to give players deathshroud if mobs kill player. + + // Zone zone = ZoneManager.findSmallestZone(this.getLoc()); + // if (zone.getSafeZone() == 0) + // PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false); + return; + } + + + // Death to other player. + // TODO Send PvP and guild/nation message + PlayerCharacter att = (PlayerCharacter) attacker; + String message = this.getFirstName(); + if (this.guild != null && (!(this.guild.getName().equals("Errant")))) + message += " of " + this.guild.getName(); + message += " was killed by " + att.getFirstName(); + if (att.guild != null && (!(att.guild.getName().equals("Errant")))) + message += " of " + att.guild.getName(); + message += "!"; + + + //see if we shold grant xp to attacker + boolean doPVPEXP = false; + long lastKill = att.getLastKillOfTarget(this.getObjectUUID()); + if ((System.currentTimeMillis() - lastKill) > MBServerStatics.PLAYER_KILL_XP_TIMER) + if (attacker.getLevel() > 39 && this.getLevel() > 39) { + Guild aN = null; + Guild tN = null; + if (attacker.getGuild() != null) + aN = attacker.getGuild().getNation(); + if (this.getGuild() != null) + tN = this.getGuild().getNation(); + if (aN == null || tN == null || aN.isEmptyGuild() || Guild.sameGuild(aN, tN) || this.isDeathShroud()) { + //skip giving xp if same guild or attacker is errant, or target is in death shroud. + } else { + doPVPEXP = true; + } + } + //apply death shroud to non safeholds. + Zone zone = ZoneManager.findSmallestZone(this.getLoc()); + + //DeathShroud + + if (zone.getSafeZone() == 0) + PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false); + + if (doPVPEXP) { + Group g = GroupManager.getGroup((PlayerCharacter) attacker); + Experience.doExperience((PlayerCharacter) attacker, this, g); + } + + ChatManager.chatPVP(message); + + /* + Update kill / death tracking lists + Each character on list is unique. Only once! + */ + + PlayerCharacter aggressorCharacter = (PlayerCharacter) attacker; + + boolean containsVictim = true; + boolean containsAttacker = true; + + containsVictim = aggressorCharacter.pvpKills.contains(this.getObjectUUID()); + containsAttacker = aggressorCharacter.pvpKills.contains(this.getObjectUUID()); + + // Rorate attacker's kill list + + if ((aggressorCharacter.pvpKills.size() == 10) && containsVictim == false) + aggressorCharacter.pvpKills.removeLast(); + + if (containsVictim == false) + aggressorCharacter.pvpKills.addFirst(this.getObjectUUID()); + + // Rotate the poor victim's deathlist + + if ((this.pvpDeaths.size() == 10) && containsAttacker == false) + this.pvpDeaths.removeLast(); + + if (containsAttacker == false) + this.pvpDeaths.addFirst(this.getObjectUUID()); + + // DataWarehouse: store pvp event + + PvpRecord pvpRecord = PvpRecord.borrow((PlayerCharacter) attacker, this, this.getLoc(), doPVPEXP); + DataWarehouse.pushToWarehouse(pvpRecord); + + // Mark kill time in killmap + + att.updateKillMap(this.getObjectUUID()); + } + + @Override + public void killCharacter(String reason) { + + killCleanup(); + Zone zone = ZoneManager.findSmallestZone(this.getLoc()); + + if (zone.getSafeZone() == 0) + PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false); + + // Send death message if needed + if (reason.equals("Water")) { + + TargetedActionMsg targetedActionMsg = new TargetedActionMsg(this, true); + Dispatch dispatch = Dispatch.borrow(this, targetedActionMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + String message = this.getFirstName(); + + if (this.guild != null && (!(this.guild.getName().equals("Errant")))) + message += " of " + this.guild.getName(); + else + message += this.getLastName(); + message += " was killed by water!"; + + ChatManager.chatPVP(message); + + } + } + + private void killCleanup() { + this.stopMovement(this.getLoc()); + + this.health.set(-1); + //remove pet + if (this.pet != null) + this.dismissPet(); + + NPCManager.dismissNecroPets(this); + // remove flight job. + + this.setTakeOffTime(0); + this.setDesiredAltitude(0); + this.altitude = (float) 0; + + // Release Mine Claims + + Mine.releaseMineClaims(this); + + this.getCharItemManager().closeTradeWindow(); + + //increment live counter. This is to prevent double kills from casts + this.liveCounter++; + + //remove any effects + try { + this.clearEffects(); + } catch (Exception e) { + Logger.error("PlayerCharacter.KillCleanup", e.getMessage()); + } + + //remove the SIT flag + this.setSit(false); + + + // sends a kill message to ensure the Player falls over. + + this.respawnLock.writeLock().lock(); + + try { + if (SessionManager.getPlayerCharacterByID(this.getObjectUUID()) == null && !this.enteredWorld) { + WorldGrid.RemoveWorldObject(this); + this.respawn(false, false, true); + } else { + TargetedActionMsg killmsg = new TargetedActionMsg(this, true); + DispatchMessage.dispatchMsgToInterestArea(this, killmsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + } + } catch (Exception e) { + Logger.error(e); + } finally { + this.respawnLock.writeLock().unlock(); + } + + // TODO damage equipped items + if (this.charItemManager != null) + this.charItemManager.damageAllGear(); + + // TODO cleanup any timers + //recalculate inventory weights + if (this.charItemManager != null) { + this.charItemManager.endTrade(true); + this.charItemManager.calculateWeights(); + this.charItemManager.updateInventory(); + } + + + } + + public void updateKillMap(int target) { + this.killMap.put(target, System.currentTimeMillis()); + } + + public long getLastKillOfTarget(int target) { + if (this.killMap.containsKey(target)) + return this.killMap.get(target); + return 0L; + } + + public boolean isDeathShroud() { + return this.effects != null && this.effects.containsKey("DeathShroud"); + } + + public void setSafeMode() { + PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, -1661758934, 40, false); + } + + public boolean safemodeInvis() { + + if (!this.effects.containsKey("Invisible")) + return false; + + Effect eff = this.effects.get("Invisible"); + + if (eff == null) + return false; + + return eff.getEffectToken() == -1661751254; + + } + + public void respawn(boolean setAlive, boolean enterWorld, boolean makeCorpse) { + + // Recalculate everything + + + this.recalculatePlayerStats(true); + this.setCombat(false); + + // Set Health to 1/4 max + + + Corpse corpse = null; + + if (makeCorpse) { + try { + corpse = Corpse.makeCorpse(this, enterWorld); + } catch (Exception e) { + Logger.error(e); + } + //if we're not making corpse, just purge inventory. used for characters dead while logged out. + } + + if (!setAlive) { + if (corpse == null && makeCorpse) { + Logger.error("Corpse not created."); + } else { + if (makeCorpse && corpse != null) { + InterestManager.forceLoad(corpse); + } + } + return; + } + + this.setHealth((float) (healthMax * .25)); + this.isAlive.set(true); + + + // Put player in safe mode + // Teleport the player to his bind loc + // or to a ruin as apporpriate. + + Building bindBuilding = BuildingManager.getBuildingFromCache(this.getBindBuildingID()); + + if (enterWorld) { + this.stopMovement(this.getBindLoc()); + } else if (bindBuilding != null) { + if (bindBuilding.getParentZone().equals(ZoneManager.findSmallestZone(this.getLoc()))) + this.teleport(Ruins.getRandomRuin().getLocation()); + else + this.teleport(this.getBindLoc()); + } else // no bind building found for player, teleport to ruins. + this.teleport(Ruins.getRandomRuin().getLocation()); + + this.lastUpdateTime = System.currentTimeMillis(); + this.lastStamUpdateTime = System.currentTimeMillis(); + + this.update(); + + PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, -1661758934, 40, false); + + if (corpse == null && makeCorpse) { + Logger.error("Corpse not created."); + } else { + if (makeCorpse && corpse != null) { + InterestManager.forceLoad(corpse); + } + } + } + + public Effect addCityEffect(String name, EffectsBase eb, int trains, int duration, boolean onEnter, City city) { + JobContainer jc = null; + if (onEnter) { + NoTimeJob ntj = new NoTimeJob(this, name, eb, trains); //infinite timer + ntj.setEffectSourceType(city.getObjectType().ordinal()); + ntj.setEffectSourceID(city.getObjectUUID()); + jc = new JobContainer(ntj); + } else { + FinishSpireEffectJob fsej = new FinishSpireEffectJob(this, name, eb, trains); + fsej.setEffectSourceType(city.getObjectType().ordinal()); + fsej.setEffectSourceID(city.getObjectUUID()); + jc = JobScheduler.getInstance().scheduleJob(fsej, duration); + } + + if (this.effects.get(name) != null) + this.effects.get(name).cancelJob(); + + Effect eff = new Effect(jc, eb, trains); + this.effects.put(name, eff); + applyAllBonuses(); + eff.sendSpireEffect(this.getClientConnection(), onEnter); + return eff; + } + + /** + * @return the race + */ + public Race getRace() { + return race; + } + + public int getRaceID() { + if (race != null) + return race.getRaceRuneID(); + return 0; + } + + /** + * @return the baseClass + */ + public BaseClass getBaseClass() { + return baseClass; + } + + public int getBaseClassID() { + if (baseClass != null) + return baseClass.getObjectUUID(); + return 0; + } + + public int getBaseClassToken() { + if (this.baseClass == null) + return 0; + else + return this.baseClass.getToken(); + } + + public boolean setBaseClass(int value) { + BaseClass bs = BaseClass.getBaseClass(value); + if (bs != null) { + this.baseClass = bs; + return true; + } + return false; + } + + @Override + public Vector3fImmutable getBindLoc() { + + Vector3fImmutable bindLocation; + + // Return garbage and early exit if this is the login server. + // getBindLoc() does a TOL lookup, which also then loads the + // city and other garbage not needed on the login server. + + if (ConfigManager.serverType.equals(ServerType.LOGINSERVER)) + return Vector3fImmutable.ZERO; + + Building bindBuilding = PlayerCharacter.getUpdatedBindBuilding(this); + + //handle rented room binds. + + if (bindBuilding == null) { + bindLocation = Enum.Ruins.getRandomRuin().getLocation(); + return bindLocation; + } + + bindLocation = BuildingManager.GetBindLocationForBuilding(bindBuilding); + + if (bindLocation == null) + bindLocation = Enum.Ruins.getRandomRuin().getLocation(); + + return bindLocation; + + } + + public int getInventoryCapacity() { + return statStrBase * 3; + } + + public int getInventoryCapacityRemaining() { + return (this.getInventoryCapacity() - this.charItemManager.getInventoryWeight()); + } + + /** + * @return the PromotionClass + */ + public PromotionClass getPromotionClass() { + return promotionClass; + } + + public int getPromotionClassID() { + if (promotionClass != null) + return promotionClass.getObjectUUID(); + return 0; + } + + public boolean setPromotionClass(int value) { + + PromotionClass promotionClass = PromotionClass.GetPromtionClassFromCache(value); + + if (promotionClass == null) + return false; + + + if (!DbManager.PlayerCharacterQueries.SET_PROMOTION_CLASS(this, value)) + return false; + + this.promotionClass = promotionClass; + + // Warehouse this event + CharacterRecord.updatePromotionClass(this); + return true; + } + + /** + * @return the skinColor + */ + public byte getSkinColor() { + return skinColor; + } + + /** + * @return the hairColor + */ + public byte getHairColor() { + return hairColor; + } + + /** + * @return the beardColor + */ + public byte getBeardColor() { + return beardColor; + } + + /** + * @return the lfGroup + */ + public boolean isLfGroup() { + return lfGroup; + } + + public int getIsLfGroupAsInt() { + if (lfGroup) + return 2; + return 1; + } + + public final void toggleLFGroup() { + this.lfGroup = !this.lfGroup; + } + + public final void toggleLFGuild() { + this.lfGuild = !this.lfGuild; + } + + public final void toggleRecruiting() { + this.recruiting = !this.recruiting; + } + + public final boolean isLFGroup() { + return this.lfGroup; + } + + public final void setLFGroup(final boolean value) { + this.lfGroup = value; + } + + public final boolean isLFGuild() { + return this.lfGuild; + } + + public final void setLFGuild(final boolean value) { + this.lfGuild = value; + } + + public final boolean isRecruiting() { + return this.recruiting; + } + + public final void setRecruiting(final boolean value) { + this.recruiting = value; + } + + /** + * @return the lfGuild + */ + public boolean isLfGuild() { + return lfGuild; + } + + public final int getHeadlightsAsInt() { + if (this.lfGroup) + if (this.lfGuild) + if (this.recruiting) + return 14; // LFGroup + LFGuild + Recruiting + else + return 6; // LFGroup + LFGuild + else if (this.recruiting) + return 10; // LFGroup + Recruiting + else + return 2; // LFGroup only + else if (this.lfGuild) + if (this.recruiting) + return 12; // LFGuild + Recruiting + else + return 4; // LFGuild only + else if (this.recruiting) + return 8; // Recruiting only + else + return 0; // No Headlights + } + + public int getIsLfGuildAsInt() { + if (lfGuild) + return 2; + return 1; + } + + public int getStrMax() { + return this.statStrMax; + } + + public int getDexMax() { + return this.statDexMax; + } + + public int getConMax() { + return this.statConMax; + } + + public int getIntMax() { + return this.statIntMax; + } + + public int getSpiMax() { + return this.statSpiMax; + } + + public void addStr(int amount) { + + boolean worked = false; + short newStr = (short) 0; + while (!worked) { + + if ((this.unusedStatPoints - this.trainedStatPoints) <= 0) + return; + + newStr = (short) (this.statStrBase + amount); + short mod = (short) this.strMod.get(); + short newStrMod = (short) (mod + amount); + + if (newStr > this.statStrMax) { + newStrMod += (this.statStrMax - newStr); + newStr = this.statStrMax; + } + worked = this.strMod.compareAndSet(mod, newStrMod); + } + this.trainedStatPoints++; + this.statStrBase = newStr; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + } + + public void addDex(int amount) { + + boolean worked = false; + short newDex = (short) 0; + + while (!worked) { + + if ((this.unusedStatPoints - this.trainedStatPoints) <= 0) + return; + + newDex = (short) (this.statDexBase + amount); + short mod = (short) this.dexMod.get(); + short newDexMod = (short) (mod + amount); + + if (newDex > this.statDexMax) { + newDexMod += (this.statDexMax - newDex); + newDex = this.statDexMax; + } + + worked = this.dexMod.compareAndSet(mod, newDexMod); + } + this.trainedStatPoints++; + this.statDexBase = newDex; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + } + + public void addCon(int amount) { + boolean worked = false; + short newCon = (short) 0; + while (!worked) { + + if ((this.unusedStatPoints - this.trainedStatPoints) <= 0) + return; + + newCon = (short) (this.statConBase + amount); + short mod = (short) this.conMod.get(); + short newConMod = (short) (mod + amount); + + if (newCon > this.statConMax) { + newConMod += (this.statConMax - newCon); + newCon = this.statConMax; + } + worked = this.conMod.compareAndSet(mod, newConMod); + } + this.trainedStatPoints++; + this.statConBase = newCon; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + } + + public void addInt(int amount) { + boolean worked = false; + short newInt = (short) 0; + while (!worked) { + + if ((this.unusedStatPoints - this.trainedStatPoints) <= 0) + return; + + newInt = (short) (this.statIntBase + amount); + short mod = (short) this.intMod.get(); + short newIntMod = (short) (mod + amount); + + if (newInt > this.statIntMax) { + newIntMod += (this.statIntMax - newInt); + newInt = this.statIntMax; + } + worked = this.intMod.compareAndSet(mod, newIntMod); + } + this.trainedStatPoints++; + this.statIntBase = newInt; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + } + + public void addSpi(int amount) { + boolean worked = false; + short newSpi = (short) 0; + + while (!worked) { + + if ((this.unusedStatPoints - this.trainedStatPoints) <= 0) + return; + + newSpi = (short) (this.statSpiBase + amount); + short mod = (short) this.spiMod.get(); + short newSpiMod = (short) (mod + amount); + + if (newSpi > this.statSpiMax) { + newSpiMod += (this.statSpiMax - newSpi); + newSpi = this.statSpiMax; + } + worked = this.spiMod.compareAndSet(mod, newSpiMod); + } + this.trainedStatPoints++; + this.statSpiBase = newSpi; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + } + + public boolean refineStr() { + boolean worked = false; + short newStr = (short) 0; + + while (!worked) { + + newStr = (short) (this.statStrBase - 1); + short mod = (short) this.strMod.get(); + + if (mod == 0) + return false; + + short newStrMod = (short) (mod - 1); + + if (newStr < this.statStrMin) + return false; + + if (!canRefineLower(MBServerStatics.STAT_STR_ID)) + return false; + + worked = this.strMod.compareAndSet(mod, newStrMod); + } + this.trainedStatPoints--; + this.statStrBase = newStr; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + return true; + } + + public boolean refineDex() { + boolean worked = false; + short newDex = (short) 0; + + while (!worked) { + newDex = (short) (this.statDexBase - 1); + short mod = (short) this.dexMod.get(); + + if (mod == 0) + return false; + + short newDexMod = (short) (mod - 1); + + if (newDex < this.statDexMin) + return false; + + if (!canRefineLower(MBServerStatics.STAT_DEX_ID)) + return false; + + worked = this.dexMod.compareAndSet(mod, newDexMod); + } + this.trainedStatPoints--; + this.statDexBase = newDex; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + return true; + } + + public boolean refineCon() { + boolean worked = false; + short newCon = (short) 0; + + while (!worked) { + newCon = (short) (this.statConBase - 1); + short mod = (short) this.conMod.get(); + + if (mod == 0) + return false; + + short newConMod = (short) (mod - 1); + + if (newCon < this.statConMin) + return false; + + if (!canRefineLower(MBServerStatics.STAT_CON_ID)) + return false; + + worked = this.conMod.compareAndSet(mod, newConMod); + } + this.trainedStatPoints--; + this.statConBase = newCon; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + return true; + } + + public boolean refineInt(RefineMsg msg) { + boolean worked = false; + short newInt = (short) 0; + + while (!worked) { + newInt = (short) (this.statIntBase - 1); + short mod = (short) this.intMod.get(); + + if (mod == 0) + return false; + short newIntMod = (short) (mod + - 1); + + if (newInt < this.statIntMin) + return false; + + if (!canRefineLower(MBServerStatics.STAT_INT_ID)) + return false; + + worked = this.intMod.compareAndSet(mod, newIntMod); + } + this.trainedStatPoints--; + this.statIntBase = newInt; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + + verifySkillMax(msg); + + this.applyBonuses(); + this.calculateSkills(); + return true; + } + + public boolean refineSpi() { + boolean worked = false; + short newSpi = (short) 0; + while (!worked) { + newSpi = (short) (this.statSpiBase - 1); + short mod = (short) this.spiMod.get(); + if (mod == 0) + return false; + short newSpiMod = (short) (mod - 1); + if (newSpi < this.statSpiMin) + return false; + if (!canRefineLower(MBServerStatics.STAT_SPI_ID)) + return false; + worked = this.spiMod.compareAndSet(mod, newSpiMod); + } + this.trainedStatPoints--; + this.statSpiBase = newSpi; + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + this.applyBonuses(); + this.calculateSkills(); + return true; + } + + //this verifies stat doesn't fall too low to keep runes applied while refining + private boolean canRefineLower(int stat) { + for (CharacterRune cr : this.runes) { + if (cr != null) { + RuneBase rb = cr.getRuneBase(); + if (rb != null) { + ArrayList attrs = rb.getAttrs(); + + if (attrs != null) + for (RuneBaseAttribute rba : attrs) { + int attrID = rba.getAttributeID(); + int mod = rba.getModValue(); + if (stat == MBServerStatics.STAT_STR_ID) { + if (attrID == MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statStrBase <= mod)) + return false; + } else if (stat == MBServerStatics.STAT_DEX_ID) { + if (attrID == MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statDexBase <= mod)) + return false; + } else if (stat == MBServerStatics.STAT_CON_ID) { + if (attrID == MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statConBase <= mod)) + return false; + } else if (stat == MBServerStatics.STAT_INT_ID) { + if (attrID == MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statIntBase <= mod)) + return false; + } else if (stat == MBServerStatics.STAT_SPI_ID) + if (attrID == MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statSpiBase <= mod)) + return false; + } + } + } + } + + return true; + } + + //checked on refining int to see if skills need refined also. + private void verifySkillMax(RefineMsg msg) { + + ConcurrentHashMap skills = getSkills(); + + //make sure no skills are over the max number of trains + int maxTrains = CharacterSkill.getMaxTrains((int) this.statIntBase); + + RefineMsg rm = new RefineMsg(msg.getNpcType(), msg.getNpcID(), 0, 0); + + for (CharacterSkill skill : skills.values()) { + + while (skill.getNumTrains() > maxTrains) { + boolean worked = skill.refine(this, false); //refine skill, do not recalculate everything + if (worked) { + rm.setToken(skill.getToken()); + + Dispatch dispatch = Dispatch.borrow(this, rm); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + } else { + Logger.error("Failed to force refine of skill " + skill.getObjectUUID() + " by character " + this.getObjectUUID()); + break; + } + } + } + } + + public int getClassToken() { + if (this.promotionClass != null) + return this.promotionClass.getToken(); + else if (this.baseClass != null) + return this.baseClass.getToken(); + return 0; + } + + public int getRaceToken() { + + if (this.race == null) + return 0; + + return this.race.getToken(); + } + + public void setLastTarget(GameObjectType type, int id) { + this.lastTargetType = type; + this.lastTargetID = id; + } + + public GameObjectType getLastTargetType() { + return this.lastTargetType; + } + + public int getLastTargetID() { + return this.lastTargetID; + } + + /* + * Serializing + */ + + public synchronized int getBindBuildingID() { + return this.bindBuildingID; + } + + public synchronized void setBindBuildingID(int value) { + DbManager.PlayerCharacterQueries.SET_BIND_BUILDING(this, value); + this.bindBuildingID = value; + } + + public AbstractGameObject getLastTarget() { + if (this.lastTargetType == GameObjectType.unknown) + return null; + + switch (this.lastTargetType) { + // Make sure these only return an object that is + // already in the GOM, and doesn't reload from the DB + case PlayerCharacter: + return DbManager.getFromCache(GameObjectType.PlayerCharacter, this.lastTargetID); + + case Building: + return DbManager.getFromCache(GameObjectType.Building, this.lastTargetID); + + case NPC: + return NPC.getFromCache(this.lastTargetID); + + case Mob: + return Mob.getFromCache(this.lastTargetID); + + case Item: + return DbManager.getFromCache(GameObjectType.Item, this.lastTargetID); + + case Corpse: + return DbManager.getFromCache(GameObjectType.Corpse, this.lastTargetID); + + default: + + // Ignore exception for MobLoot? ***Check + if (this.lastTargetType != GameObjectType.MobLoot) + Logger.error("getLastTarget() unhandled object type: " + + this.lastTargetType.toString()); + } + return null; + } + + public Vector3fImmutable getLastStaticLoc() { + return this.lastStaticLoc; + } + + public void setLastStaticLoc(Vector3fImmutable value) { + this.lastStaticLoc = value; + } + + public int getHidden() { + return this.hidden; + } + + public void setHidden(int value) { + this.hidden = value; + } + + public int getSeeInvis() { + if (this.getDebug(8)) //<-added for see invis debug devcmd + return 10000; + return this.seeInvis; + } + + public void setSeeInvis(int value) { + this.seeInvis = value; + } + + public long getLastPlayerAttackTime() { + return this.lastPlayerAttackTime; + } + + public void setLastPlayerAttackTime() { + this.lastPlayerAttackTime = System.currentTimeMillis(); + } + + public NPC getLastNPCDialog() { + return this.lastNPCDialog; + } + + public void setLastNPCDialog(NPC value) { + this.lastNPCDialog = value; + } + + + public Mob getPet() { + return this.pet; + } + + public void setPet(Mob mob) { + + if (mob == null) + return; + + this.pet = mob; + } + + public void dismissPet() { + + if (this.pet != null) { + this.pet.dismiss(); + this.pet = null; + } + } + + //called to verify player has correct item equipped for casting. + public boolean validEquip(int slot, String type) { + + if (this.charItemManager == null) + return false; + + Item item = this.charItemManager.getEquipped(slot); + + if (item == null) + return false; + + ItemBase ib = item.getItemBase(); + if (ib != null) { + + if ((ib.getType().equals(ItemType.WEAPON)) + && (ib.getSkillRequired().equals(type) || ib.getMastery().equals(type))) + return true; + + return (ib.getType().equals(ItemType.ARMOR)) + && (ib.getSkillRequired().equals(type)); + } + + return false; + } + + public short getPCLevel() { + short level = (short) Experience.getLevel(this.exp); + if (this.promotionClass == null && level >= 10) + return (short) 10; + + if (this.overFlowEXP > 0) + return this.level; + + return level; + } + + @Override + public float getSpeed() { + + float speed; + + if (this.getAltitude() > 0) + if (this.walkMode) { + speed = race.getRaceType().getRunSpeed().getFlyWalk(); + } else { + speed = race.getRaceType().getRunSpeed().getFlyRun(); + } + else if (this.lastSwimming == true) + speed = MBServerStatics.SWIMSPEED; + else if (this.walkMode) { + if (this.isCombat()) + speed = race.getRaceType().getRunSpeed().getWalkCombat(); + else + speed = race.getRaceType().getRunSpeed().getWalkStandard(); + } else { + if (this.isCombat()) + speed = race.getRaceType().getRunSpeed().getRunCombat(); + else + speed = race.getRaceType().getRunSpeed().getRunStandard(); + } + + float endSpeed = speed * this.speedMod; + + if (endSpeed > 41 && !this.isCSR) + endSpeed = 41; + + return endSpeed; + } + + public synchronized void grantXP(int xp) { + // Stop players from getting experience past the cap + if (this.exp + xp >= Experience.getBaseExperience(MBServerStatics.LEVELCAP)) + xp = Experience.getBaseExperience(MBServerStatics.LEVELCAP) - this.exp + 1; + + if (xp == 0) + xp = 1; + + boolean isNewLevel = false; + boolean charReloadRequired = false; + int remainingXP = xp; + int neededXP = 0; + + // handle players that have not yet promoted. + ClientConnection origin = this.getClientConnection(); + + //not promoted at level 10, start checking for negative EXP + if (this.promotionClass == null && this.getLevel() == 10) { + + if (this.getExp() == Experience.getBaseExperience(11)) { + if (this.overFlowEXP == 110000) + return; + + if (this.overFlowEXP + xp > 110000) { + remainingXP = 110000 - this.overFlowEXP; + this.overFlowEXP = 110000; + + + } else { + this.overFlowEXP += remainingXP; + } + + GrantExperienceMsg gem = new GrantExperienceMsg(this, remainingXP); + Dispatch dispatch = Dispatch.borrow(this, gem); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); + return; + //didnt reach level 11 EXP to start overflow, add exp normally till we get here; + } else { + + //Player exp begins negative exp, add remaing exp after level 11 to overflow + if (this.getExp() + remainingXP >= Experience.getBaseExperience(11)) { + + this.overFlowEXP = remainingXP - (Experience.getBaseExperience(11) - this.getExp()); + this.exp = Experience.getBaseExperience(11); + + GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP); + Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); + return; + + //didnt reach negative exp yet, just do normal exp gain. + } else { + this.exp += remainingXP; + GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP); + remainingXP = 0; + Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); + return; + } + } + } + + if (this.overFlowEXP > 0) { + + int nextLevel; + + if (level == 10) + nextLevel = 12; + else + nextLevel = level + 2; + + int nextLevelEXP = Experience.getBaseExperience(nextLevel); + + // if overflow > 0, u have level 11 experience + overflow, but level is still 10 due to just promoting. + //Use level + 2 experience for next level. + this.overFlowEXP += 1; + + if (this.getExp() + this.overFlowEXP >= nextLevelEXP) { + + int expToNextLevel = nextLevelEXP - this.getExp(); + this.overFlowEXP -= expToNextLevel; + this.exp += expToNextLevel; + this.level++; + charReloadRequired = true; + + GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, 1); + Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + SetObjectValueMsg upm = new SetObjectValueMsg(this, 9); + DispatchMessage.dispatchMsgToInterestArea(this, upm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + checkGuildStatus(); + this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); + // double overflow exp used up, remaining overflow will just add to level + 1. + } else if (this.getExp() + this.overFlowEXP >= Experience.getBaseExperience(level + 1)) { + int nextExperience = Experience.getBaseExperience(level + 1) + this.overFlowEXP; + this.exp = nextExperience; + this.level++; + charReloadRequired = true; + this.overFlowEXP = 0; + GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, 1); + Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + SetObjectValueMsg upm = new SetObjectValueMsg(this, 9); + DispatchMessage.dispatchMsgToInterestArea(this, upm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + checkGuildStatus(); + this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); + } + + } else { + // Hand out each Level one at a time. + isNewLevel = Experience.getLevel(exp + remainingXP) > this.getLevel(); + + if (isNewLevel) { + neededXP = Experience.getBaseExperience(this.getLevel() + 1) - this.exp; + + charReloadRequired = true; + this.exp += neededXP; + this.level++; + + GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, neededXP); + Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + remainingXP -= neededXP; + + //Send newLevel. + SetObjectValueMsg upm = new SetObjectValueMsg(this, 9); + DispatchMessage.dispatchMsgToInterestArea(this, upm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + checkGuildStatus(); + } else { + + this.exp += remainingXP; + GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP); + remainingXP = 0; + Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + + this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); + } + } + + if (charReloadRequired) { + this.update(); + this.incVer(); + this.recalculate(); + this.calculateMaxHealthManaStamina(); + this.setHealth(this.healthMax); + this.mana.set(this.manaMax); + this.stamina.set(this.staminaMax); + //LoadJob.reloadCharacter(this); + DbManager.PlayerCharacterQueries.SET_PROPERTY(this, "char_experience", this.exp); + // updateDatabase(); + DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(this.getObjectUUID(), "character"); + } + } + + //This checks if a player meets the requirements to be in current guild. + public void checkGuildStatus() { + + Guild g = this.guild; + + if (g == null || g.isEmptyGuild() || GuildStatusController.isGuildLeader(guildStatus)) + return; + + //check level + int curLevel = (int) getPCLevel(); + if (curLevel < g.getRepledgeMin() || curLevel >= g.getRepledgeKick()) { + //TODO kick from guild + g.removePlayer(this, GuildHistoryType.LEAVE); + ChatManager.chatGuildInfo(this, "You no longer meet the level requirements for the guild."); + } + } + + public void calculateSpeedMod() { + // get base race speed modifer + + + //this is retarded. *** Refactor + // if (this.race != null) { + // int ID = this.race.getObjectUUID(); + // if (ID == 2004 || ID == 2005) + // this.raceRunMod = 1.21f; // centaur run bonus 22% + //// else if (ID == 2017) + //// this.raceRunMod = 1.14f; // mino run bonus 15% + // else + // this.raceRunMod = 1; + // } else + // this.raceRunMod = 1; + + + float bonus = 1f; + + // // TODO: hardcoded, as didnt have time to introduce DB column to base object + // if (baseClass.getName().equals("Fighter") || baseClass.getName().equals("Rogue")) + // bonus += .05f; + + // get running skill + if (this.skills != null) { + CharacterSkill running = this.skills.get("Running"); + if (running != null) { + + float runningBonus = (float) (Math.log(Math.round(running.getModifiedAmount()) * .01f) / Math.log(2) * .50f); + runningBonus = (float) (Math.pow(2, runningBonus) - 1); + runningBonus += 1; + runningBonus *= .25f; + bonus += runningBonus; + + } + } + + if (this.bonuses != null) + // get rune and effect bonuses + bonus += this.bonuses.getFloatPercentNullZero(ModType.Speed, SourceType.None); + + // TODO get equip bonus + this.update(); + this.speedMod = bonus; + } + + public ClientConnection getClientConnection() { + return SessionManager.getClientConnection(this); + } + + public String getCombinedName() { + return this.getName(); + } + + public long getLastGuildToInvite() { + return this.lastGuildToInvite; + } + + public void setLastGuildToInvite(int value) { + this.lastGuildToInvite = value; + } + + public boolean getFollow() { + return this.follow; + } + + public void setFollow(boolean value) { + this.follow = value; + } + + public boolean toggleFollow() { + this.follow = !this.follow; + return this.follow; + } + + public int getLastGroupToInvite() { + return this.lastGroupToInvite; + } + + public void setLastGroupToInvite(int value) { + this.lastGroupToInvite = value; + } + + @Override + public float getAltitude() { + if (this.altitude < 0) + this.altitude = 0; + + //player has reached desired altitude, return normal altitude. + if (this.getTakeOffTime() == 0) + return this.altitude; + + //sanity check if desired altitude is the same as current altitude. return desired altitude. + if (this.altitude == this.getDesiredAltitude()) { + return this.getDesiredAltitude(); + } + + //calculate how much the player has moved up + float amountMoved = (System.currentTimeMillis() - this.getTakeOffTime()) * MBServerStatics.FLY_RATE; //FUCK DIVIDING + + //Player is moving up + if (this.getDesiredAltitude() > this.altitude) { + + //if amount moved passed desiredAltitude, return the desired altitude. + if (this.altitude + amountMoved >= this.getDesiredAltitude()) + return this.getDesiredAltitude(); + + return this.altitude + amountMoved; + //Player is moving down + } else { + //if amount moved passed desiredAltitude, return the desired altitude. + if (this.altitude - amountMoved <= this.getDesiredAltitude()) + return this.getDesiredAltitude(); + return this.altitude - amountMoved; + } + + + } + + public void setAltitude(float value) { + this.altitude = value; + } + + public HashSet getLoadedObjects() { + return this.loadedObjects; + } + + public HashSet getLoadedStaticObjects() { + return this.loadedStaticObjects; + } + + public void setLoadedStaticObjects(HashSet value) { + this.loadedStaticObjects = value; + } + + public boolean isTeleportMode() { + return teleportMode; + } + + public void setTeleportMode(boolean teleportMode) { + this.teleportMode = teleportMode; + } + + // public ConcurrentHashMap + // getRecycleTimers() { + // return this.recycleTimers; + // } + // public UsePowerJob getLastPower() { + // return this.lastPower; + // } + // public void setLastPower(UsePowerJob value) { + // this.lastPower = value; + // } + // public void clearLastPower() { + // this.lastPower = null; + // } + public long chatFloodTime(int chatOpcode, long chatTimeMilli, int qtyToSave) { + if (qtyToSave < 1) + return 0L; // disabled + LinkedList times = null; + long oldestTime; + synchronized (chatChanFloodList) { + if (!chatChanFloodList.containsKey(chatOpcode)) { + times = new LinkedList<>(); + for (int i = 0; i < qtyToSave; i++) { + times.add(0L); + } + chatChanFloodList.put(chatOpcode, times); + } else + times = chatChanFloodList.get(chatOpcode); + oldestTime = times.getLast(); + times.removeLast(); + times.addFirst(chatTimeMilli); + } + return oldestTime; + } + + public void addIgnoredPlayer(Account ac, String name) { + if (ac == null) + return; + int acID = ac.getObjectUUID(); + if (acID < 1) + return; + if (ignoredPlayerIDs == null) + return; + if (acID == getObjectUUID()) + return; // yourself + + ignoredPlayerIDs.put(acID, name); + } + + public void removeIgnoredPlayer(Account ac) { + if (ac == null) + return; + int acID = ac.getObjectUUID(); + if (acID < 1) + return; + if (ignoredPlayerIDs == null) + return; + if (acID == getObjectUUID()) + return; // yourself + + ignoredPlayerIDs.remove(acID); + } + + public boolean isIgnoringPlayer(PlayerCharacter pc) { + + if (pc == null) + return false; + + if (pc.account == null) + return false; + + return isIgnoringPlayer(pc.account); + } + + public boolean isIgnoringPlayer(Account ac) { + if (ac == null) + return false; + int acID = ac.getObjectUUID(); + if (acID < 1) + return false; + return ignoredPlayerIDs.containsKey(acID); + } + + public String[] getIgnoredPlayerNames() { + int size = ignoredPlayerIDs.size(); + String[] ary = new String[size]; + for (int i = 0; i < size; i++) { + // ary[i] = PlayerCharacter.getFirstName(ignoredPlayerIDs.get(i)); + ary[i] = ignoredPlayerIDs.get(i); + } + return ary; + } + + public int getStrMod() { + return this.strMod.get(); + } + + public int getDexMod() { + return this.dexMod.get(); + } + + public int getConMod() { + return this.conMod.get(); + } + + public int getIntMod() { + return this.intMod.get(); + } + + public int getSpiMod() { + return this.spiMod.get(); + } + + public boolean isMale() { + if (this.race == null) + return true; + return (this.race.getRaceType().getCharacterSex().equals(CharacterSex.MALE)); + } + + public boolean canSee(PlayerCharacter tar) { + + if (tar == null) + return false; + + if (this.equals(tar)) + return true; + + return this.getSeeInvis() >= tar.hidden && !tar.safemodeInvis(); + } + + public void recalculatePlayerStats(boolean initialized) { + + //calculate base stats + calculateBaseStats(); + + //calculate base skills + CharacterSkill.updateAllBaseAmounts(this); + calculateModifiedStats(); + + //calculate modified skills + CharacterSkill.updateAllModifiedAmounts(this); + this.updateScaleHeight(); + + //calculate modified stats + + + //calculate ATR, damage and defense + calculateAtrDefenseDamage(); + + //calculate movement bonus + calculateSpeedMod(); + + // recalculate Max Health/Mana/Stamina + calculateMaxHealthManaStamina(); + + // recalculate Resists + Resists.calculateResists(this); + + } + + /** + * @ Recalculate player after promoting or gaining a level + */ + public void recalculate() { + this.applyBonuses(); + this.trainsAvailable.set(CharacterSkill.getTrainsAvailable(this)); + if (this.trainsAvailable.get() < 0) + recalculateTrains(); + //this.resists.calculateResists(this); + + // calculate skills and powers. Make sure none are missing. + this.calculateSkills(); + + // calculate powers again. See if any new powers unlocked + this.calculateSkills(); + } + + //This is run to auto-fix any overage on skill training. + private void recalculateTrains() { + int trainsAvailable = CharacterSkill.getTrainsAvailable(this); + if (trainsAvailable < 0) { + + //refine powers first, run twice to catch any prereqs + ConcurrentHashMap powers = this.getPowers(); + for (int i = 0; i < 2; i++) { + for (CharacterPower p : powers.values()) { + if (trainsAvailable >= 0) + return; + while (p.getTrains() > 0 && p.refine(this)) { + trainsAvailable++; + if (trainsAvailable >= 0) + return; + } + } + } + + //refine skills + ConcurrentHashMap skills = this.getSkills(); + for (CharacterSkill s : skills.values()) { + if (trainsAvailable >= 0) + return; + while (s.getNumTrains() > 0 && s.refine(this)) { + if (CharacterSkill.getTrainsAvailable(this) >= 0) + return; + } + } + } + } + + /** + * @ Calculates Base Stats Call this when modifying stats or adding/removing + * runes + */ + public void calculateBaseStats() { + if (this.race == null || this.baseClass == null) + // Logger.getInstance().log( LogEventType.ERROR, + // "PlayerCharacter.updateBaseStats: Missing race or baseclass for Player " + // + this.getUUID()); + return; + + // get base stats and total available + int strMin = this.race.getStrStart() + this.baseClass.getStrMod() - 5; + int dexMin = this.race.getDexStart() + this.baseClass.getDexMod() - 5; + int conMin = this.race.getConStart() + this.baseClass.getConMod() - 5; + int intMin = this.race.getIntStart() + this.baseClass.getIntMod() - 5; + int spiMin = this.race.getSpiStart() + this.baseClass.getSpiMod() - 5; + int str = this.race.getStrStart() + this.baseClass.getStrMod() + this.strMod.get(); + int dex = this.race.getDexStart() + this.baseClass.getDexMod() + this.dexMod.get(); + int con = this.race.getConStart() + this.baseClass.getConMod() + this.conMod.get(); + int intt = this.race.getIntStart() + this.baseClass.getIntMod() + this.intMod.get(); + int spi = this.race.getSpiStart() + this.baseClass.getSpiMod() + this.spiMod.get(); + int strMax = this.race.getStrMax(); + int dexMax = this.race.getDexMax(); + int conMax = this.race.getConMax(); + int intMax = this.race.getIntMax(); + int spiMax = this.race.getSpiMax(); + int available = this.race.getStartingPoints() - this.strMod.get() - this.dexMod.get() - this.conMod.get() - this.intMod.get() - this.spiMod.get(); + if (level < 20) + available += (level - 1) * 5; + else if (level < 30) + available += 90 + (level - 19) * 4; + else if (level < 40) + available += 130 + (level - 29) * 3; + else if (level < 50) + available += 160 + (level - 39) * 2; + else + available += 180 + (level - 49); + + // modify for any runes applied. + for (CharacterRune rune : this.runes) { + if (rune.getRuneBase() == null) + // Logger.getInstance().log( LogEventType.ERROR, + // "PlayerCharacter.updateBaseStats: Missing runebase for rune " + // + rune.getUUID()); + continue; + ArrayList attrs = rune.getRuneBase().getAttrs(); + if (attrs == null) + // Logger.getInstance().log( LogEventType.ERROR, + // "PlayerCharacter.updateBaseStats: Missing attributes for runebase " + // + rune.getRuneBase().getUUID()); + continue; + for (RuneBaseAttribute abr : attrs) { + int attrID = abr.getAttributeID(); + int value = abr.getModValue(); + switch (attrID) { + case MBServerStatics.RUNE_COST_ATTRIBUTE_ID: + available -= value; + break; + case MBServerStatics.RUNE_STR_ATTRIBUTE_ID: + str += value; + strMin += value; + break; + case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID: + dex += value; + dexMin += value; + break; + case MBServerStatics.RUNE_CON_ATTRIBUTE_ID: + con += value; + conMin += value; + break; + case MBServerStatics.RUNE_INT_ATTRIBUTE_ID: + intt += value; + intMin += value; + break; + case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID: + spi += value; + spiMin += value; + break; + case MBServerStatics.RUNE_STR_MAX_ATTRIBUTE_ID: + strMax += value; + break; + case MBServerStatics.RUNE_DEX_MAX_ATTRIBUTE_ID: + dexMax += value; + break; + case MBServerStatics.RUNE_CON_MAX_ATTRIBUTE_ID: + conMax += value; + break; + case MBServerStatics.RUNE_INT_MAX_ATTRIBUTE_ID: + intMax += value; + break; + case MBServerStatics.RUNE_SPI_MAX_ATTRIBUTE_ID: + spiMax += value; + break; + default: + } + } + + //Set titles based on rune.. + switch (rune.getRuneBaseID()) { + default: + break; + + case 2901: //CSR 1 + this.title = CharacterTitle.CSR_1; + break; + case 2902: //CSR 1 + this.title = CharacterTitle.CSR_2; + break; + case 2903: //CSR 1 + this.title = CharacterTitle.CSR_3; + break; + case 2904: //CSR 1 + this.title = CharacterTitle.CSR_4; + break; + + case 2910: //Wolfpack Developer + this.title = CharacterTitle.DEVELOPER; + break; + case 2911: //QA Test Rune + this.title = CharacterTitle.QA; + break; + } + } + + //hack check. Make sure available does not go below 0. + //subtract from each stat until available is 0 or greater. + if (available < 0) { + while (this.spiMod.get() > 0 && available < 0) { + this.spiMod.decrementAndGet(); + spi--; + available++; + } + while (this.conMod.get() > 0 && available < 0) { + this.conMod.decrementAndGet(); + con--; + available++; + } + while (this.strMod.get() > 0 && available < 0) { + this.strMod.decrementAndGet(); + str--; + available++; + } + while (this.dexMod.get() > 0 && available < 0) { + this.dexMod.decrementAndGet(); + dex--; + available++; + } + while (this.intMod.get() > 0 && available < 0) { + this.intMod.decrementAndGet(); + intt--; + available++; + } + + //update database + this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS); + } + + this.statStrBase = (short) str; + this.statDexBase = (short) dex; + this.statConBase = (short) con; + this.statIntBase = (short) intt; + this.statSpiBase = (short) spi; + this.statStrMax = (short) (strMax); + this.statDexMax = (short) (dexMax); + this.statConMax = (short) (conMax); + this.statIntMax = (short) (intMax); + this.statSpiMax = (short) (spiMax); + this.statStrMin = (short) strMin; + this.statDexMin = (short) dexMin; + this.statConMin = (short) conMin; + this.statIntMin = (short) intMin; + this.statSpiMin = (short) spiMin; + this.unusedStatPoints = (short) available; + this.trainedStatPoints = 0; + + // Testing, allow characters to have more stats then normal for formula checking + if (this.statStrBase > this.statStrMax) + this.statStrMax = this.statStrBase; + if (this.statDexBase > this.statDexMax) + this.statDexMax = this.statDexBase; + if (this.statConBase > this.statConMax) + this.statConMax = this.statConBase; + if (this.statIntBase > this.statIntMax) + this.statIntMax = this.statIntBase; + if (this.statSpiBase > this.statSpiMax) + this.statSpiMax = this.statSpiBase; + + // Modified stats must be recalculated when base stats are + //calculateModifiedStats(); + //update hide and seeInvis levels + if (this.bonuses != null) { + this.hidden = (int) bonuses.getFloat(ModType.Invisible, SourceType.None); + this.seeInvis = (int) bonuses.getFloat(ModType.SeeInvisible, SourceType.None); + } else { + this.hidden = (byte) 0; + this.seeInvis = (byte) 0; + } + + //check is player is a CSR + this.isCSR = this.containsCSRRune(); + } + + private boolean containsCSRRune() { + + if (this.race != null && this.race.getRaceType().equals(RaceType.CSRMALE)) + return true; + + if (this.baseClass != null && this.baseClass.getObjectUUID() > 2900 && this.baseClass.getObjectUUID() < 2905) + return true; + + if (this.promotionClass != null && this.promotionClass.getObjectUUID() > 2900 && this.promotionClass.getObjectUUID() < 2905) + return true; + + if (this.runes == null) + return false; + + for (CharacterRune rune : this.runes) { + + if (rune == null || rune.getRuneBase() == null) + continue; + + RuneBase rb = rune.getRuneBase(); + + if (rb.getObjectUUID() > 2900 && rb.getObjectUUID() < 2905) + return true; + if (rb.getObjectUUID() == 2910) + return true; + + } + return false; + } + + public boolean isCSR() { + return this.isCSR; + } + + public void setAsciiLastName(boolean value) { + this.asciiLastName = value; + } + + public boolean _asciiLastName() { + return this.asciiLastName; + } + + /** + * @ Calculates Modified Stats Call this when changing equipment or + * add/removing effect. skips base stat modification. + */ + public void calculateModifiedStats() { + float strVal = this.statStrBase; + float dexVal = this.statDexBase; + float conVal = this.statConBase; + float intVal = this.statIntBase; + float spiVal = this.statSpiBase; + + this.dexPenalty = getDexPenalty(); + + // TODO modify for equipment + if (this.bonuses != null) { + // modify for effects + strVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Strength)); + dexVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Dexterity)); + conVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Constitution)); + intVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Intelligence)); + spiVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Spirit)); + + // apply dex penalty for armor + dexVal *= this.dexPenalty; + + // modify percent amounts. DO THIS LAST! + strVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Strength)); + dexVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Dexterity)); + conVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Constitution)); + intVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Intelligence)); + spiVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Spirit)); + + } else + // apply dex penalty for armor + dexVal *= this.dexPenalty; + + // Set current stats + this.statStrCurrent = (strVal < 1) ? (short) 1 : (short) strVal; + this.statDexCurrent = (dexVal < 1) ? (short) 1 : (short) dexVal; + this.statConCurrent = (conVal < 1) ? (short) 1 : (short) conVal; + this.statIntCurrent = (intVal < 1) ? (short) 1 : (short) intVal; + this.statSpiCurrent = (spiVal < 1) ? (short) 1 : (short) spiVal; + + // recalculate skills + //CharacterSkill.updateAllBaseAmounts(this); + // recalculate Max Health/Mana/Stamina + //calculateMaxHealthManaStamina(); + // recalculate Resists + //this.resists.calculateResists(this); + } + + public float getDexPenalty() { + + if (this.charItemManager == null || this.charItemManager.getEquipped() == null) { + Logger.error("Player " + this.getObjectUUID() + " missing equipment"); + return 1f; + } + + ConcurrentHashMap equipped = this.charItemManager.getEquipped(); + float dexPenalty = 0f; + dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_HELMET)); + dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_CHEST)); + dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_ARMS)); + dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_GLOVES)); + dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_LEGGINGS)); + dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_FEET)); + return (1 - (dexPenalty / 100)); + } + + public int getStrForClient() { + return this.statStrCurrent - this.race.getStrStart() - this.baseClass.getStrMod(); + } + + public int getDexForClient() { + return this.statDexCurrent - this.race.getDexStart() - this.baseClass.getDexMod(); + } + + public int getConForClient() { + return this.statConCurrent - this.race.getConStart() - this.baseClass.getConMod(); + } + + public int getIntForClient() { + return this.statIntCurrent - this.race.getIntStart() - this.baseClass.getIntMod(); + } + + public int getSpiForClient() { + return this.statSpiCurrent - this.race.getSpiStart() - this.baseClass.getSpiMod(); + } + + public int getTrainsAvailable() { + return this.trainsAvailable.get(); + } + + public void modifyTrainsAvailable(int amount) { + boolean worked = false; + while (!worked) { + int old = this.trainsAvailable.get(); + int newVal = old + amount; + // if (newVal < 0) + // newVal = 0; + worked = this.trainsAvailable.compareAndSet(old, newVal); + } + } + + // Reset any data that should not persist from a previous session + public void resetDataAtLogin() { + loadedObjects.clear(); + loadedStaticObjects.clear(); + lastStaticLoc = Vector3fImmutable.ZERO; + setLastTarget(GameObjectType.unknown, 0); + this.follow = false; + } + + /** + * @ Calculates Atr (both hands) Defense, and Damage for pc + */ + public void calculateAtrDefenseDamage() { + if (this.charItemManager == null || this.charItemManager.getEquipped() == null || this.skills == null) { + Logger.error("Player " + this.getObjectUUID() + " missing skills or equipment"); + defaultAtrAndDamage(true); + defaultAtrAndDamage(false); + this.defenseRating = 0; + return; + } + ConcurrentHashMap equipped = this.charItemManager.getEquipped(); + + // // Reset passives + // if (this.bonuses != null) { + // this.bonuses.setBool("Block", false); + // this.bonuses.setBool("Parry", false); + // if (this.baseClass != null && this.baseClass.getUUID() == 2502) + // this.bonuses.setBool("Dodge", true); + // else + // this.bonuses.setBool("Dodge", false); + // } + // calculate atr and damage for each hand + calculateAtrDamageForWeapon(equipped.get(MBServerStatics.SLOT_MAINHAND), true, equipped.get(MBServerStatics.SLOT_OFFHAND)); + calculateAtrDamageForWeapon(equipped.get(MBServerStatics.SLOT_OFFHAND), false, equipped.get(MBServerStatics.SLOT_MAINHAND)); + + // No Defense while in DeathShroud + if (this.effects != null && this.effects.containsKey("DeathShroud")) + this.defenseRating = (short) 0; + else { + // calculate defense for equipment + float defense = this.statDexCurrent * 2; + defense += getShieldDefense(equipped.get(MBServerStatics.SLOT_OFFHAND)); + defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_HELMET)); + defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_CHEST)); + defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_ARMS)); + defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_GLOVES)); + defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_LEGGINGS)); + defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_FEET)); + defense += getWeaponDefense(equipped); + + if (this.bonuses != null) { + // add any bonuses + defense += (short) this.bonuses.getFloat(ModType.DCV, SourceType.None); + + // Finally multiply any percent modifiers. DO THIS LAST! + float pos_Bonus = this.bonuses.getFloatPercentPositive(ModType.DCV, SourceType.None); + defense = (short) (defense * (1 + pos_Bonus)); + + //Lucky rune applies next + //applied runes will be calculated and added to the normal bonuses. no need for this garbage anymore + //defense = (short) (defense * (1 + ((float) this.bonuses.getShort("rune.Defense") / 100))); + + //and negative percent modifiers + //already done... + float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.DCV, SourceType.None); + defense = (short) (defense * (1 + neg_Bonus)); + + } else + // TODO add error log here + Logger.error("Error: missing bonuses"); + + defense = (defense < 1) ? 1 : defense; + this.defenseRating = (short) (defense + 0.5f); + } + } + + /** + * @ Calculates Atr, and Damage for each weapon + */ + private void calculateAtrDamageForWeapon(Item weapon, boolean mainHand, Item otherHand) { + + // make sure weapon exists + boolean noWeapon = false; + ItemBase wb = null; + if (weapon == null) + noWeapon = true; + else { + ItemBase ib = weapon.getItemBase(); + if (ib == null) + noWeapon = true; + else if (!ib.getType().equals(ItemType.WEAPON)) { + defaultAtrAndDamage(mainHand); + return; + } else + wb = ib; + } + float skillPercentage, masteryPercentage; + float mastDam; + float min, max; + float speed = 20f; + boolean strBased = false; + + ItemBase wbMain = (weapon != null) ? weapon.getItemBase() : null; + ItemBase wbOff = (otherHand != null) ? otherHand.getItemBase() : null; + + // get skill percentages and min and max damage for weapons + if (noWeapon) { + if (mainHand) { + Item off = this.charItemManager.getEquipped().get(MBServerStatics.SLOT_OFFHAND); + if (off != null && off.getItemBase() != null && off.getItemBase().getType().equals(ItemType.WEAPON)) + this.rangeHandOne = 10 * (1 + (this.statStrBase / 600)); // Set + // to + // no + // weapon + // range + else + this.rangeHandOne = -1; // set to do not attack + } else + this.rangeHandTwo = -1; // set to do not attack + + skillPercentage = getModifiedAmount(this.skills.get("Unarmed Combat")); + masteryPercentage = getModifiedAmount(this.skills.get("Unarmed Combat Mastery")); + if (masteryPercentage == 0f) + mastDam = CharacterSkill.getQuickMastery(this, "Unarmed Combat Mastery"); + else + mastDam = masteryPercentage; + // TODO Correct these + min = 1; + max = 3; + } else { + if (mainHand) + this.rangeHandOne = weapon.getItemBase().getRange() * (1 + (this.statStrBase / 600)); + else + this.rangeHandTwo = weapon.getItemBase().getRange() * (1 + (this.statStrBase / 600)); + + if (this.bonuses != null) { + float range_bonus = 1 + this.bonuses.getFloatPercentAll(ModType.WeaponRange, SourceType.None); + + if (mainHand) + this.rangeHandOne *= range_bonus; + else + this.rangeHandTwo *= range_bonus; + + } + skillPercentage = getModifiedAmount(this.skills.get(wb.getSkillRequired())); + masteryPercentage = getModifiedAmount(this.skills.get(wb.getMastery())); + if (masteryPercentage == 0f) + mastDam = 0f; + // mastDam = CharacterSkill.getQuickMastery(this, wb.getMastery()); + else + mastDam = masteryPercentage; + min = (float) wb.getMinDamage(); + max = (float) wb.getMaxDamage(); + strBased = wb.isStrBased(); + + // + // Add parry bonus for weapon and allow parry if needed + + // // Only Fighters and Thieves can Parry + // if ((this.baseClass != null && this.baseClass.getUUID() == 2500) + // || (this.promotionClass != null && this.promotionClass.getUUID() == 2520)) { + // if (wbMain == null || wbMain.getRange() < MBServerStatics.RANGED_WEAPON_RANGE) + // if (wbOff == null || wbOff.getRange() < MBServerStatics.RANGED_WEAPON_RANGE) + // this.bonuses.setBool("Parry", true); + // } + // } + } + + if (this.effects != null && this.effects.containsKey("DeathShroud")) + // No Atr in deathshroud. + if (mainHand) + this.atrHandOne = (short) 0; + else + this.atrHandTwo = (short) 0; + else { + // calculate atr + float atr = 0; + atr += (int) skillPercentage * 4f; //<-round down skill% - + atr += (int) masteryPercentage * 3f; + if (this.statStrCurrent > this.statDexCurrent) + atr += statStrCurrent / 2; + else + atr += statDexCurrent / 2; + + // add in any bonuses to atr + if (this.bonuses != null) { + // Add any base bonuses + atr += this.bonuses.getFloat(ModType.OCV, SourceType.None); + + // Finally use any multipliers. DO THIS LAST! + float pos_Bonus = (1 + this.bonuses.getFloatPercentPositive(ModType.OCV, SourceType.None)); + atr *= pos_Bonus; + + // next precise + //runes will have their own bonuses. + // atr *= (1 + ((float) this.bonuses.getShort("rune.Attack") / 100)); + + //and negative percent modifiers + float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.OCV, SourceType.None); + + atr *= (1 + neg_Bonus); + } + + atr = (atr < 1) ? 1 : atr; + + // set atr + if (mainHand) + this.atrHandOne = (short) (atr + 0.5f); + else + this.atrHandTwo = (short) (atr + 0.5f); + } + + //calculate speed + if (wb != null) + speed = wb.getSpeed(); + else + speed = 20f; //unarmed attack speed + if (weapon != null) + speed *= (1 + this.bonuses.getFloatPercentAll(ModType.WeaponSpeed, SourceType.None)); + speed *= (1 + this.bonuses.getFloatPercentAll(ModType.AttackDelay, SourceType.None)); + if (speed < 10) + speed = 10; + + //add min/max damage bonuses for weapon + if (weapon != null) { + // Add any base bonuses + + min += weapon.getBonus(ModType.MinDamage, SourceType.None); + max += weapon.getBonus(ModType.MaxDamage, SourceType.None); + + min += weapon.getBonus(ModType.MeleeDamageModifier, SourceType.None); + max += weapon.getBonus(ModType.MeleeDamageModifier, SourceType.None); + // Finally use any multipliers. DO THIS LAST! + + float percentMinDamage = 1; + float percentMaxDamage = 1; + + percentMinDamage += weapon.getBonusPercent(ModType.MinDamage, SourceType.None); + percentMinDamage += weapon.getBonusPercent(ModType.MeleeDamageModifier, SourceType.None); + + percentMaxDamage += weapon.getBonusPercent(ModType.MaxDamage, SourceType.None); + percentMaxDamage += weapon.getBonusPercent(ModType.MeleeDamageModifier, SourceType.None); + + + min *= percentMinDamage; + max *= percentMaxDamage; + } + + //if duel wielding, cut damage by 30% + if (otherHand != null) { + ItemBase ibo = otherHand.getItemBase(); + if (ibo != null && ibo.getType().equals(ItemType.WEAPON)) { + min *= 0.7f; + max *= 0.7f; + } + } + + // calculate damage + float minDamage; + float maxDamage; + float pri = (strBased) ? (float) this.statStrCurrent : (float) this.statDexCurrent; + float sec = (strBased) ? (float) this.statDexCurrent : (float) this.statStrCurrent; + minDamage = (float) (min * ((0.0315f * Math.pow(pri, 0.75f)) + (0.042f * Math.pow(sec, 0.75f)) + (0.01f * ((int) skillPercentage + (int) mastDam)))); + maxDamage = (float) (max * ((0.0785f * Math.pow(pri, 0.75f)) + (0.016f * Math.pow(sec, 0.75f)) + (0.0075f * ((int) skillPercentage + (int) mastDam)))); + minDamage = (float) ((int) (minDamage + 0.5f)); //round to nearest decimal + maxDamage = (float) ((int) (maxDamage + 0.5f)); //round to nearest decimal + + // Half damage if in death shroud + if (this.effects != null && this.effects.containsKey("DeathShroud")) { + minDamage *= 0.5f; + maxDamage *= 0.5f; + } + + // add in any bonuses to damage + if (this.bonuses != null) { + // Add any base bonuses + minDamage += this.bonuses.getFloat(ModType.MinDamage, SourceType.None); + maxDamage += this.bonuses.getFloat(ModType.MaxDamage, SourceType.None); + + minDamage += this.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None); + maxDamage += this.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None); + // Finally use any multipliers. DO THIS LAST! + + float percentMinDamage = 1; + float percentMaxDamage = 1; + + percentMinDamage += this.bonuses.getFloatPercentAll(ModType.MinDamage, SourceType.None); + percentMinDamage += this.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None); + + percentMaxDamage += this.bonuses.getFloatPercentAll(ModType.MaxDamage, SourceType.None); + percentMaxDamage += this.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None); + + minDamage *= percentMinDamage; + maxDamage *= percentMaxDamage; + + } + + // set damages + if (mainHand) { + this.minDamageHandOne = (int) minDamage; + this.maxDamageHandOne = (int) maxDamage; + this.speedHandOne = speed; + } else { + this.minDamageHandTwo = (int) minDamage; + this.maxDamageHandTwo = (int) maxDamage; + this.speedHandTwo = speed; + } + } + + /** + * @ Calculates Defense for shield + */ + private float getShieldDefense(Item shield) { + if (shield == null) + return 0; + ItemBase ab = shield.getItemBase(); + if (ab == null || !ab.isShield()) + return 0; + CharacterSkill blockSkill = this.skills.get("Block"); + float skillMod; + if (blockSkill == null) { + skillMod = 0; + } else + skillMod = blockSkill.getModifiedAmount(); + + float def = ab.getDefense(); + //apply item defense bonuses + if (shield != null) { + def += shield.getBonus(ModType.DR, SourceType.None); + def *= (1 + shield.getBonusPercent(ModType.DR, SourceType.None)); + + } + + // float val = ((float)ab.getDefense()) * (1 + (skillMod / 100)); + return (def * (1 + ((int) skillMod / 100f))); + } + + public void setPassives() { + if (this.bonuses != null) { + ConcurrentHashMap equipped = this.charItemManager.getEquipped(); + Item off = equipped.get(MBServerStatics.SLOT_OFFHAND); + Item main = equipped.get(MBServerStatics.SLOT_MAINHAND); + ItemBase wbMain = null; + ItemBase wbOff = null; + if (main != null) + wbMain = main.getItemBase(); + if (off != null) + wbOff = off.getItemBase(); + + //set block if block found + this.bonuses.setBool(ModType.Block, SourceType.None, false); + if (this.baseClass != null && (this.baseClass.getObjectUUID() == 2500 || this.baseClass.getObjectUUID() == 2501)) + if (off != null && off.getItemBase() != null && off.getItemBase().isShield()) + this.bonuses.setBool(ModType.Block, SourceType.None, true); + + //set dodge if rogue + if (this.baseClass != null && this.baseClass.getObjectUUID() == 2502) + this.bonuses.setBool(ModType.Dodge, SourceType.None, true); + else + this.bonuses.setBool(ModType.Dodge, SourceType.None, false); + + //set parry if fighter or thief and no invalid weapon found + this.bonuses.setBool(ModType.Parry, SourceType.None, false); + if ((this.baseClass != null && this.baseClass.getObjectUUID() == 2500) + || (this.promotionClass != null && this.promotionClass.getObjectUUID() == 2520)) + if (wbMain == null || wbMain.getRange() < MBServerStatics.RANGED_WEAPON_RANGE) + if (wbOff == null || wbOff.getRange() < MBServerStatics.RANGED_WEAPON_RANGE) + this.bonuses.setBool(ModType.Parry, SourceType.None, true); + + } + + } + + /** + * @ Calculates Defense for armor + */ + private float getArmorDefense(Item armor) { + + if (armor == null) + return 0; + + ItemBase ib = armor.getItemBase(); + + if (ib == null) + return 0; + + if (!ib.getType().equals(ItemType.ARMOR)) + return 0; + if (ib.getSkillRequired().isEmpty()) + return ib.getDefense(); + CharacterSkill armorSkill = this.skills.get(ib.getSkillRequired()); + if (armorSkill == null) { + Logger.error("Player " + this.getObjectUUID() + + " has armor equipped without the nescessary skill to equip it"); + return ib.getDefense(); + } + + float def = ib.getDefense(); + //apply item defense bonuses + if (armor != null) { + def += armor.getBonus(ModType.DR, SourceType.None); + def *= (1 + armor.getBonusPercent(ModType.DR, SourceType.None)); + } + + + return (def * (1 + ((int) armorSkill.getModifiedAmount() / 50f))); + } + + /** + * @ Calculates Defense for weapon + */ + private float getWeaponDefense(ConcurrentHashMap equipped) { + Item weapon = equipped.get(MBServerStatics.SLOT_MAINHAND); + ItemBase wb = null; + CharacterSkill skill, mastery; + float val = 0; + boolean unarmed = false; + if (weapon == null) { + weapon = equipped.get(MBServerStatics.SLOT_OFFHAND); + if (weapon == null || weapon.getItemBase().isShield()) + unarmed = true; + else + wb = weapon.getItemBase(); + } else + wb = weapon.getItemBase(); + if (wb == null) + unarmed = true; + if (unarmed) { + skill = this.skills.get("Unarmed Combat"); + mastery = this.skills.get("Unarmed Combat Mastery"); + } else { + skill = this.skills.get(wb.getSkillRequired()); + mastery = this.skills.get(wb.getMastery()); + } + if (skill != null) + val += (int) skill.getModifiedAmount() / 2f; + if (mastery != null) + val += (int) mastery.getModifiedAmount() / 2f; + return val; + } + + //Call this function to recalculate granted skills and powers for player + public synchronized void calculateSkills() { + //tell the player to applyBonuses because something has changed + + runSkillCalc(); + + //start running the skill/power calculations + } + + //Don't call this function directly. linked from pc.calculateSkills() + //through SkillCalcJob. Designed to only run from one worker thread + public void runSkillCalc() { + try { + + //see if any new skills or powers granted + CharacterSkill.calculateSkills(this); + // calculate granted Trains in powers. + CharacterPower.grantTrains(this); + //see if any new powers unlocked from previous check + CharacterPower.calculatePowers(this); + + } catch (Exception e) { + } + + } + + //calculate item bonuses here + public void calculateItemBonuses() { + if (this.charItemManager == null || this.bonuses == null) + return; + ConcurrentHashMap equipped = this.charItemManager.getEquipped(); + for (Item item : equipped.values()) { + ItemBase ib = item.getItemBase(); + if (ib == null) + continue; + //TODO add effect bonuses in here for equipped items + } + } + + /** + * @ Defaults ATR, Defense and Damage for player + */ + private void defaultAtrAndDamage(boolean mainHand) { + if (mainHand) { + this.atrHandOne = 0; + this.minDamageHandOne = 0; + this.maxDamageHandOne = 0; + this.rangeHandOne = -1; + this.speedHandOne = 20; + } else { + this.atrHandTwo = 0; + this.minDamageHandTwo = 0; + this.maxDamageHandTwo = 0; + this.rangeHandTwo = -1; + this.speedHandTwo = 20; + } + } + + public void calculateMaxHealthManaStamina() { + float h = 1f; + float m = 0f; + float s = 0f; + float baseHealth = 15f; + float baseMana = 5f; + float baseStamina = 1f; + float promoHealth = 0f; + float promoMana = 0f; + float promoStamina = 0f; + float raceHealth = 0f; + float raceMana = 0f; + float raceStamina = 0f; + float toughness = 0f; + float athletics = 0f; + + //get baseclass modifiers + if (this.baseClass != null) { + baseHealth = this.baseClass.getHealthMod(); + baseMana = this.baseClass.getManaMod(); + baseStamina = this.baseClass.getStaminaMod(); + } else { + //TODO log error here + } + + //get promotion modifiers + if (this.promotionClass != null) { + promoHealth = this.promotionClass.getHealthMod(); + promoMana = this.promotionClass.getManaMod(); + promoStamina = this.promotionClass.getStaminaMod(); + } + + // next get racial modifer + if (this.race != null) { + raceHealth += this.race.getHealthBonus(); + raceMana += this.race.getManaBonus(); + raceStamina += this.race.getStaminaBonus(); + } else { + //TODO log error here + } + + //Get level modifers + float f = 0; + float g = 0; + if (this.level < 10 || this.promotionClass == null) + f = this.level; + else if (this.level < 20) { + f = this.level; + g = this.level - 9; + } else if (level < 30) { + f = (float) (19 + (this.level - 19) * 0.8); + g = (float) (10 + (this.level - 19) * 0.8); + } else if (level < 40) { + f = (float) (27 + (this.level - 29) * 0.6); + g = (float) (18 + (this.level - 29) * 0.6); + } else if (level < 50) { + f = (float) (33 + (this.level - 39) * 0.4); + g = (float) (24 + (this.level - 39) * 0.4); + } else if (level < 60) { + f = (float) (37 + (this.level - 49) * 0.2); + g = (float) (28 + (this.level - 49) * 0.2); + } else { + f = (float) (39 + (this.level - 59) * 0.1); + g = (float) (30 + (this.level - 59) * 0.1); + } + + //get toughness and athletics amount + if (this.skills != null) { + if (this.skills.containsKey("Toughness")) + toughness = this.skills.get("Toughness").getModifiedAmount(); + if (this.skills.containsKey("Athletics")) + athletics = this.skills.get("Athletics").getModifiedAmount(); + } + + h = (((f * baseHealth) + (g * promoHealth)) * (0.3f + (0.005f * this.statConCurrent)) + (this.statConCurrent + raceHealth)) * (1 + (int) toughness / 400f); + m = ((f * baseMana) + (g * promoMana)) * (0.3f + (0.005f * this.statSpiCurrent)) + (this.statSpiCurrent + raceMana); + s = (((f * baseStamina) + (g * promoStamina)) * (0.3f + (0.005f * this.statConCurrent)) + (this.statConCurrent + raceStamina)) * (1 + (int) athletics / 300f); + + // s = f * (baseStamina + 1.75f) * .5f + this.statConCurrent + raceStamina; + // Apply any bonuses from runes and effects + if (this.bonuses != null) { + + + //apply effects + h += this.bonuses.getFloat(ModType.HealthFull, SourceType.None); + m += this.bonuses.getFloat(ModType.ManaFull, SourceType.None); + s += this.bonuses.getFloat(ModType.StaminaFull, SourceType.None); + + h *= (1 + this.bonuses.getFloatPercentAll(ModType.HealthFull, SourceType.None)); + m *= (1 + this.bonuses.getFloatPercentAll(ModType.ManaFull, SourceType.None)); + s *= (1 + this.bonuses.getFloatPercentAll(ModType.StaminaFull, SourceType.None)); + + } + + // Set max health, mana and stamina + if (h > 0) + this.healthMax = h; + else + this.healthMax = 1; + if (m > -1) + this.manaMax = m; + else + this.manaMax = 0; + if (s > -1) + this.staminaMax = s; + else + this.staminaMax = 0; + + // Update health, mana and stamina if needed + if (this.getCurrentHitpoints() > this.healthMax) + this.setHealth(this.healthMax); + if (this.mana.get() > this.manaMax) + this.mana.set(this.manaMax); + if (this.stamina.get() > this.staminaMax) + this.stamina.set(staminaMax); + } + + @Override + public float getPassiveChance(String type, int attackerLevel, boolean fromCombat) { + if (this.skills == null || this.bonuses == null) + return 0f; + + ModType modType = ModType.GetModType(type); + + // must be allowed to use this passive + if (!this.bonuses.getBool(modType, SourceType.None)) + return 0f; + + // must not be stunned + if (this.bonuses.getBool(ModType.Stunned, SourceType.None)) + return 0f; + + // Get base skill amount + CharacterSkill sk = this.skills.get(type); + float amount; + if (sk == null) + amount = CharacterSkill.getQuickMastery(this, type); + else + amount = sk.getModifiedAmount(); + + // Add bonuses + amount += this.bonuses.getFloat(modType, SourceType.None); + + // Add item bonuses and return + if (type.equals(ModType.Dodge) && !fromCombat) + return ((amount / 4) - attackerLevel + this.getLevel()) / 4; + else + return (amount - attackerLevel + this.getLevel()) / 4; + } + + public float getPassiveChance1(ModType modType, SourceType sourceType, int attackerLevel, boolean fromCombat) { + if (this.skills == null || this.bonuses == null) + return 0f; + + // must be allowed to use this passive + if (!this.bonuses.getBool(modType, sourceType)) + return 0f; + + // must not be stunned + if (this.bonuses.getBool(ModType.Stunned, SourceType.None)) + return 0f; + + // Get base skill amount + CharacterSkill sk = this.skills.get(sourceType.name()); + float amount; + if (sk == null) + amount = CharacterSkill.getQuickMastery(this, modType.name()); + else + amount = sk.getModifiedAmount(); + + // Add bonuses + amount += this.bonuses.getFloat(modType, sourceType); + + // Add item bonuses and return + if (sourceType.equals(SourceType.Dodge) && !fromCombat) + return ((amount / 4) - attackerLevel + this.getLevel()) / 4; + else + return (amount - attackerLevel + this.getLevel()) / 4; + } + + public float getRegenModifier(ModType type) { + float regen = 1f; + + if (this.bonuses != null) + // get regen bonus from effects + regen = this.bonuses.getRegen(type); + return regen; + } + + @Override + public boolean canBeLooted() { + return !this.isAlive(); + } + + public void ResetLevel(short targetLevel) { + + if (targetLevel > 13) { + ChatManager.chatSystemError(this, "Please choose a level between 1 and 13."); + return; + } + this.promotionClass = null; + if (targetLevel > 10) { + this.level = 10; + this.exp = Experience.getBaseExperience(11); + int maxEXP = Experience.getBaseExperience(targetLevel); //target level exp; + this.overFlowEXP = maxEXP - this.exp; + } else { + this.level = targetLevel; + this.exp = Experience.getBaseExperience(level); + this.overFlowEXP = 0; + } + + + for (CharacterSkill skill : this.getSkills().values()) { + skill.reset(this, true); + } + + for (CharacterPower power : this.getPowers().values()) { + power.reset(this); + } + + this.recalculatePlayerStats(initialized); + this.recalculate(); + + ChatManager.chatSystemInfo(this, "Character reset to " + targetLevel + ". All training points have been refunded. Relog to update changes on client."); + + } + + @Override + public void removeFromCache() { + Logger.info("Removing " + this.getName() + " from Object Cache."); + + for (Item e : this.charItemManager.getEquipped().values()) { + e.removeFromCache(); + } + + for (Item i : this.charItemManager.getInventory(true)) { + i.removeFromCache(); + } + + for (Item b : this.charItemManager.getBank()) { + b.removeFromCache(); + } + + if (this.account.getLastCharIDUsed() == this.getObjectUUID()) + for (Item v : this.charItemManager.getVault()) { + v.removeFromCache(); + } + + for (CharacterSkill cs : this.getSkills().values()) { + cs.removeFromCache(); + } + + for (CharacterPower ps : this.getPowers().values()) { + ps.removeFromCache(); + } + + for (CharacterRune cr : this.runes) { + cr.removeFromCache(); + } + + super.removeFromCache(); + } + + public void storeIgnoreListDB() { + + } + + public void updateSkillsAndPowersToDatabase() { + if (this.skills != null) + for (CharacterSkill skill : this.skills.values()) { + DbManager.CharacterSkillQueries.UPDATE_TRAINS(skill); + if (this.powers != null) + for (CharacterPower power : this.powers.values()) { + DbManager.CharacterPowerQueries.UPDATE_TRAINS(power); + } + } + } + + @Override + public void updateDatabase() { + } + + @Override + public void runAfterLoad() { + + // Init inventory + + this.charItemManager = new CharacterItemManager(this); + + Bounds playerBounds = Bounds.borrow(); + playerBounds.setBounds(this.getLoc()); + this.setBounds(playerBounds); + } + + @Override + protected ConcurrentHashMap initializePowers() { + return DbManager.CharacterPowerQueries.GET_POWERS_FOR_CHARACTER(this); + } + + @Override + public final void setFirstName(final String name) { + super.setFirstName(name); + } + + @Override + public void setLastName(final String name) { + super.setLastName(name); + } + + @Override + public short getLevel() { + return this.getPCLevel(); + } + + @Override + public void setLevel(short targetLevel) { + + short tmpLevel; + + tmpLevel = targetLevel; + + tmpLevel = (short) Math.min(tmpLevel, 75); + + while (this.level < tmpLevel) { + grantXP(Experience.getBaseExperience(tmpLevel) - this.exp); + } + + } + + @Override + public boolean asciiLastName() { + return this._asciiLastName(); + } + + @Override + public void setGuild(Guild value) { + + if (value == null) + value = Guild.getErrantGuild(); + + int guildID = 0; + + if (!value.isEmptyGuild()) + guildID = value.getObjectUUID(); + DbManager.PlayerCharacterQueries.UPDATE_GUILD(this, guildID); + super.setGuild(value); + + // Player changed guild so let's invalidate the login server + // cache to reflect this event. + + //Update player bind location; + + Building cityTol = null; + + if (value.getOwnedCity() != null) + cityTol = value.getOwnedCity().getTOL(); + + this.setBindBuildingID(cityTol != null ? cityTol.getObjectUUID() : 0); + //update binds, checks for nation tol if guild tol == null; + PlayerCharacter.getUpdatedBindBuilding(this); + + + DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(this.getObjectUUID(), "character"); + } + + public long getSummoner(int summoner) { + synchronized (this.summoners) { + if (!this.summoners.containsKey(summoner)) + return 0; + return this.summoners.get(summoner); + } + } + + public void addSummoner(int summoner, long time) { + synchronized (this.summoners) { + this.summoners.put(summoner, time); + } + } + + public void removeSummoner(int summoner) { + synchronized (this.summoners) { + if (this.summoners.containsKey(summoner)) + this.summoners.remove(summoner); + } + } + + public boolean commandSiegeMinion(Mob toCommand) { + if (!toCommand.isSiege()) + return false; + if (toCommand.isPet() || !toCommand.isAlive()) + return false; + + if (toCommand.getGuild().getNation() != this.getGuild().getNation()) + return false; + + if (this.pet != null) { + Mob currentPet = this.pet; + if (!currentPet.isSiege()) { + + currentPet.setCombatTarget(null); + + if (currentPet.getParentZone() != null) + + currentPet.getParentZone().zoneMobSet.remove(currentPet); + + try { + currentPet.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + currentPet.playerAgroMap.clear(); + WorldGrid.RemoveWorldObject(currentPet); + DbManager.removeFromCache(currentPet); + + } else if (currentPet.isSiege()) { + currentPet.agentType = AIAgentType.MOBILE; + currentPet.setOwner(null); + currentPet.setCombatTarget(null); + if (currentPet.isAlive()) + WorldGrid.updateObject(currentPet); + } + } + + toCommand.setPet(this, false); + this.setPet(toCommand); + toCommand.setCombatTarget(null); + PetMsg petMsg = new PetMsg(6, toCommand); + Dispatch dispatch = Dispatch.borrow(this, petMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + if (toCommand.isAlive()) + WorldGrid.updateObject(toCommand); + return true; + } + + // Method is called by Server Heartbeat simulation tick. + // Stat regen and transform updates should go in here. + + public boolean isNoTeleScreen() { + return noTeleScreen; + } + + public void setNoTeleScreen(boolean noTeleScreen) { + this.noTeleScreen = noTeleScreen; + } + + private double getDeltaTime() { + + return (System.currentTimeMillis() - lastUpdateTime) * .001f; + } + + private double getStamDeltaTime() { + + return (System.currentTimeMillis() - lastStamUpdateTime) * .001f; + } + + public boolean isFlying() { + + return this.getAltitude() > 0; + + } + + public boolean isSwimming() { + + // If char is flying they aren't quite swimming + try { + if (this.isFlying()) + return false; + + Zone zone = ZoneManager.findSmallestZone(this.getLoc()); + + if (zone.getSeaLevel() != 0) { + + float localAltitude = this.getLoc().y + this.centerHeight; + if (localAltitude < zone.getSeaLevel()) + return true; + } else { + if (this.getLoc().y + this.centerHeight < 0) + return true; + } + } catch (Exception e) { + Logger.info(this.getName() + e); + } + + return false; + } + + public boolean isSwimming(Vector3fImmutable currentLoc) { + + // If char is flying they aren't quite swimming + try { + + float localAltitude = HeightMap.getWorldHeight(currentLoc); + + Zone zone = ZoneManager.findSmallestZone(currentLoc); + + if (zone.getSeaLevel() != 0) { + + if (localAltitude < zone.getSeaLevel()) + return true; + } else { + if (localAltitude < 0) + return true; + } + } catch (Exception e) { + Logger.info(this.getName() + e); + } + + return false; + } + + @Override + public void update() { + + if (this.updateLock.writeLock().tryLock()) { + try { + + if (!this.isAlive()) + return; + + updateLocation(); + updateMovementState(); + updateRegen(); + + if (this.getStamina() < 10) { + if (this.getAltitude() > 0 || this.getDesiredAltitude() > 0) { + PlayerCharacter.GroundPlayer(this); + updateRegen(); + } + } + + RealmMap.updateRealm(this); + updateBlessingMessage(); + + this.safeZone = this.isInSafeZone(); + + } catch (Exception e) { + Logger.error(e); + } finally { + this.updateLock.writeLock().unlock(); + } + } + } + + @Override + public void updateFlight() { + + if (this.getAltitude() == 0 && this.getTakeOffTime() == 0) + return; + + if (this.getTakeOffTime() == 0) + return; + + if (this.getAltitude() == this.getDesiredAltitude()) { + if (this.getDesiredAltitude() == 0) + this.syncClient(); + //landing in a building, mark altitude to 0 as player is no longer flying. + if (this.landingRegion != null) { + this.altitude = 0; + this.region = this.landingRegion; + this.loc = this.loc.setY(this.landingRegion.lerpY(this)); + } else + this.altitude = this.getDesiredAltitude(); + + this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude()); + + this.setTakeOffTime(0); + MovementManager.finishChangeAltitude(this, this.getDesiredAltitude()); + + return; + } + + this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude()); + } + + public boolean hasBoon() { + for (Effect eff : this.getEffects().values()) { + if (eff.getPowerToken() == -587743986 || eff.getPowerToken() == -1660519801 || eff.getPowerToken() == -1854683250) + return true; + } + return false; + } + + public void updateBlessingMessage() { + + if (this.getTimeStamp("RealmClaim") > System.currentTimeMillis()) + return; + + int count = 0; + + for (Effect eff : this.getEffects().values()) { + if (eff.getPowerToken() == -587743986 || eff.getPowerToken() == -1660519801 || eff.getPowerToken() == -1854683250) + count++; + } + + if (count > 0) { + this.timestamps.put("RealmClaim", DateTime.now().plusMinutes(3).getMillis()); + for (PlayerCharacter toSend : SessionManager.getAllActivePlayerCharacters()) { + ChatManager.chatSystemInfo(toSend, this.getCombinedName() + " is seeking to claim a realm and already has " + count + " blessngs!"); + } + } + } + + @Override + public void updateLocation() { + + + if (!this.isMoving()) + return; + + if (!this.isActive) + return; + + Vector3fImmutable newLoc = this.getMovementLoc(); + + if (this.isAlive() == false || this.getBonuses().getBool(ModType.Stunned, SourceType.None) || this.getBonuses().getBool(ModType.CannotMove, SourceType.None)) { + //Target is stunned or rooted. Don't move + this.stopMovement(newLoc); + this.region = AbstractWorldObject.GetRegionByWorldObject(this); + return; + } + if (newLoc.equals(this.getEndLoc())) { + this.stopMovement(newLoc); + this.region = AbstractWorldObject.GetRegionByWorldObject(this); + if (this.getDebug(1)) + ChatManager.chatSystemInfo(this, + "Arrived at End location. " + this.getEndLoc()); + return; + //Next upda + } + + setLoc(newLoc); + this.region = AbstractWorldObject.GetRegionByWorldObject(this); + + if (this.getDebug(1)) + ChatManager.chatSystemInfo(this, + "Distance to target " + this.getEndLoc().distance2D(this.getLoc()) + " speed " + this.getSpeed()); + + if (this.getStamina() < 10) + MovementManager.sendOOS(this); + + // if (MBServerStatics.MOVEMENT_SYNC_DEBUG || this.getDebug(1)) + // Logger.info("MovementManager", "Updating movement current loc:" + this.getLoc().getX() + " " + this.getLoc().getZ() + // + " end loc: " + this.getEndLoc().getX() + " " + this.getEndLoc().getZ() + " distance " + this.getEndLoc().distance2D(this.getLoc())); + + } + + @Override + public void updateMovementState() { + + + if (this.enteredWorld) { + if (!this.lastSwimming) { + boolean enterWater = PlayerCharacter.enterWater(this); + + if (enterWater) { + this.lastSwimming = enterWater; + MovementManager.sendRWSSMsg(this); + + } + } else { + if (PlayerCharacter.LeaveWater(this)) { + this.lastSwimming = false; + if (!this.isMoving()) + MovementManager.sendRWSSMsg(this); + } + + } + + boolean breathe = PlayerCharacter.CanBreathe(this); + + if (breathe != this.canBreathe) { + this.canBreathe = breathe; + // ChatManager.chatSystemInfo(this, "Breathe : " + this.canBreathe); + this.syncClient(); + } + } + + //char is flying + if (this.isFlying() == true) { + this.movementState = MovementState.FLYING; + return; + } + // Char is not moving. Set sitting or idle + if (!this.isMoving()) { + + if (this.sit == true) + this.movementState = MovementState.SITTING; + else + this.movementState = MovementState.IDLE; + + return; + } else { + this.movementState = MovementState.RUNNING; + } + + // Char is swimming // we now are saving lastSwimstate boolean, use this instead of calling getSwimming again. + if (this.lastSwimming == true) { + this.movementState = MovementState.SWIMMING; + return; + } + + // Char is moving, yet not swimming or flying he must be running + this.movementState = MovementState.RUNNING; + + } + + @Override + public void updateRegen() { + + float healthRegen = 0f; + float manaRegen = 0f; + float stamRegen = 0f; + + boolean updateClient = false; + + // Early exit if char is dead or disconnected + if ((this.isAlive() == false) + || (this.isActive() == false) || this.getLoc().x == 0 && this.getLoc().z == 0) + return; + + // Calculate Regen amount from last simulation tick + switch (this.movementState) { + + case IDLE: + + healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_IDLE) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * (getRegenModifier(ModType.HealthRecoverRate)); + + if (this.isCasting() || this.isItemCasting()) + healthRegen *= .75f; + // Characters regen mana when in only walk mode and idle + if (this.walkMode) + manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_IDLE) * getRegenModifier(ModType.ManaRecoverRate)); + else if (!this.isCasting() && !this.isItemCasting()) + manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_IDLE) * getRegenModifier(ModType.ManaRecoverRate)); + else + manaRegen = 0; + + if (!PlayerCharacter.CanBreathe(this)) + stamRegen = MBServerStatics.STAMINA_REGEN_SWIM; + else if ((!this.isCasting() && !this.isItemCasting()) || this.lastMovementState.equals(MovementState.FLYING)) + stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate); + else + stamRegen = 0; + break; + case SITTING: + healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_SIT) + MBServerStatics.HEALTH_REGEN_SIT_STATIC) * getRegenModifier(ModType.HealthRecoverRate); + manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_SIT) * (getRegenModifier(ModType.ManaRecoverRate)); + stamRegen = MBServerStatics.STAMINA_REGEN_SIT * getRegenModifier(ModType.StaminaRecoverRate); + break; + case RUNNING: + if (this.walkMode == true) { + healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate); + manaRegen = this.manaMax * MBServerStatics.MANA_REGEN_WALK * getRegenModifier(ModType.ManaRecoverRate); + stamRegen = MBServerStatics.STAMINA_REGEN_WALK; + } else { + healthRegen = 0; + manaRegen = 0; + + if (this.combat == true) + stamRegen = MBServerStatics.STAMINA_REGEN_RUN_COMBAT; + else + stamRegen = MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT; + } + break; + case FLYING: + + float seventyFive = this.staminaMax * .75f; + float fifty = this.staminaMax * .5f; + float twentyFive = this.staminaMax * .25f; + + if (this.getDesiredAltitude() == 0 && this.getAltitude() <= 10) { + if (this.isCombat()) + stamRegen = 0; + else + stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate); + } else if (!this.useFlyMoveRegen()) { + + healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_IDLE) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * (getRegenModifier(ModType.HealthRecoverRate)); + + if (this.isCasting() || this.isItemCasting()) + healthRegen *= .75f; + // Characters regen mana when in only walk mode and idle + if (this.walkMode) + manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_IDLE + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate)); + else if (!this.isCasting() && !this.isItemCasting()) + manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_IDLE + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate)); + else + manaRegen = 0; + + if (!this.isItemCasting() && !this.isCasting() || this.getTakeOffTime() != 0) + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_IDLE; + else + stamRegen = -1f; + } else if (this.walkMode == true) { + healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate); + manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_WALK) + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate)); + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_WALK; + } else { + healthRegen = 0; + manaRegen = 0; + if (this.isCombat()) + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN_COMBAT; + else + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN; + } + + float oldStamina = this.stamina.get(); + + if (FastMath.between(oldStamina, 0, twentyFive) && !this.wasTripped25) { + updateClient = true; + this.wasTripped25 = true; + this.wasTripped50 = false; + this.wasTripped75 = false; + } else if (FastMath.between(oldStamina, twentyFive, fifty) && !this.wasTripped50) { + updateClient = true; + this.wasTripped25 = false; + this.wasTripped50 = true; + this.wasTripped75 = false; + } else if (FastMath.between(oldStamina, fifty, seventyFive) && !this.wasTripped75) { + updateClient = true; + this.wasTripped25 = false; + this.wasTripped50 = false; + this.wasTripped75 = true; + } + break; + case SWIMMING: + if (this.walkMode == true) { + healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate); + manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_WALK) + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate)); + stamRegen = MBServerStatics.STAMINA_REGEN_SWIM; + } else { + healthRegen = 0; + manaRegen = 0; + stamRegen = MBServerStatics.STAMINA_REGEN_SWIM; + + if (this.combat == true) + stamRegen += MBServerStatics.STAMINA_REGEN_RUN_COMBAT; + else + stamRegen += MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT; + } + break; + } + + // Are we drowning? + if ((this.getStamina() <= 0) + && (PlayerCharacter.CanBreathe(this) == false)) + healthRegen = (this.healthMax * -.03f); + + // Multiple regen values by current deltaTime + // Logger.info("", healthRegen + ""); + healthRegen *= getDeltaTime(); + manaRegen *= getDeltaTime(); + stamRegen *= getStamDeltaTime(); + + boolean workedHealth = false; + boolean workedMana = false; + boolean workedStamina = false; + + float old, mod; + while (!workedHealth || !workedMana || !workedStamina) { + if (!this.isAlive() || !this.isActive()) + return; + if (!workedHealth) { + old = this.health.get(); + mod = old + healthRegen; + if (mod > this.healthMax) + mod = healthMax; + else if (mod <= 0) { + if (this.isAlive.compareAndSet(true, false)) + killCharacter("Water"); + return; + } + workedHealth = this.health.compareAndSet(old, mod); + } + if (!workedStamina) { + old = this.stamina.get(); + mod = old + stamRegen; + if (mod > this.staminaMax) + mod = staminaMax; + else if (mod < 0) + mod = 0; + workedStamina = this.stamina.compareAndSet(old, mod); + } + if (!workedMana) { + old = this.mana.get(); + mod = old + manaRegen; + if (mod > this.manaMax) + mod = manaMax; + else if (mod < 0) + mod = 0; + workedMana = this.mana.compareAndSet(old, mod); + } + } + + if (updateClient) + this.syncClient(); + + // Reset this char's frame time. + this.lastUpdateTime = System.currentTimeMillis(); + this.lastStamUpdateTime = System.currentTimeMillis(); + + } + + public synchronized void updateStamRegen(long time) { + + boolean disable = true; + + if (disable) + return; + + float stamRegen = 0f; + + // Early exit if char is dead or disconnected + if ((this.isAlive() == false) + || (this.isActive() == false) || this.getLoc().x == 0 && this.getLoc().z == 0) + return; + + // Calculate Regen amount from last simulation tick + switch (this.movementState) { + + case IDLE: + if (!PlayerCharacter.CanBreathe(this)) + stamRegen = MBServerStatics.STAMINA_REGEN_SWIM; + else if ((!this.isCasting() && !this.isItemCasting()) || this.lastMovementState.equals(MovementState.FLYING)) + stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate); + else + stamRegen = 0; + break; + case SITTING: + stamRegen = MBServerStatics.STAMINA_REGEN_SIT * getRegenModifier(ModType.StaminaRecoverRate); + break; + case RUNNING: + if (this.walkMode == true) { + stamRegen = MBServerStatics.STAMINA_REGEN_WALK; + } else { + if (this.combat == true) + stamRegen = MBServerStatics.STAMINA_REGEN_RUN_COMBAT; + else + stamRegen = MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT; + } + break; + case FLYING: + + if (this.getDesiredAltitude() == 0 && this.getAltitude() <= 10) { + if (this.isCombat()) + stamRegen = 0; + else + stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate); + } else if (!this.isMoving()) { + + + if (!this.isItemCasting() && !this.isCasting() || this.getTakeOffTime() != 0) + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_IDLE; + else + stamRegen = -1f; + + } else if (this.walkMode == true) { + + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_WALK; + } else { + if (this.isCombat()) + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN_COMBAT; + else + stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN; + } + break; + case SWIMMING: + if (this.walkMode == true) { + stamRegen = MBServerStatics.STAMINA_REGEN_SWIM; + } else { + stamRegen = MBServerStatics.STAMINA_REGEN_SWIM; + } + break; + } + + + // Multiple regen values by current deltaTime + // Logger.info("", healthRegen + ""); + + stamRegen *= (time * .001f); + + + boolean workedStamina = false; + + + float old, mod; + while (!workedStamina) { + if (!this.isAlive() || !this.isActive()) + return; + + if (!workedStamina) { + old = this.stamina.get(); + mod = old + stamRegen; + if (mod > this.staminaMax) + mod = staminaMax; + else if (mod < 0) + mod = 0; + workedStamina = this.stamina.compareAndSet(old, mod); + } + + } + + } + + public void syncClient() { + + ModifyHealthMsg modifyHealthMsg = new ModifyHealthMsg(null, this, 0, 1, 1, -1984683793, "", 0, 652920987); + //mhm.setOmitFromChat(0); + Dispatch dispatch = Dispatch.borrow(this, modifyHealthMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + } + + public MovementState getMovementState() { + return movementState; + } + + public boolean isHasAnniversery() { + return hasAnniversery; + } + + public void setHasAnniversery(boolean hasAnniversery) { + DbManager.PlayerCharacterQueries.SET_ANNIVERSERY(this, hasAnniversery); + this.hasAnniversery = hasAnniversery; + } + + public int getSpamCount() { + return spamCount; + } + + public void setSpamCount(int spamCount) { + this.spamCount = spamCount; + } + + public String getHash() { + return hash; + } + + public void setHash() { + + this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID()); + + // Write hash to player character table + + DataWarehouse.writeHash(DataRecordType.CHARACTER, this.getObjectUUID()); + } + + public AtomicInteger getGuildStatus() { + return guildStatus; + } + + public int getLastRealmID() { + return lastRealmID; + } + + public void setLastRealmID(int lastRealmID) { + this.lastRealmID = lastRealmID; + } + + public int getSubRaceID() { + return subRaceID; + } + + public void setSubRaceID(int subRaceID) { + this.subRaceID = subRaceID; + } + + public ArrayList getGuildHistory() { + return guildHistory; + } + + public void setGuildHistory(ArrayList guildHistory) { + this.guildHistory = guildHistory; + } + + public void moveTo(Vector3fImmutable endLoc) { + this.setInBuilding(-1); + this.setInFloorID(-1); + MoveToPointMsg moveToMsg = new MoveToPointMsg(); + moveToMsg.setStartCoord(this.getLoc()); + moveToMsg.setEndCoord(endLoc); + moveToMsg.setInBuilding(-1); + moveToMsg.setUnknown01(-1); + moveToMsg.setSourceType(GameObjectType.PlayerCharacter.ordinal()); + moveToMsg.setSourceID(this.getObjectUUID()); + + Dispatch dispatch = Dispatch.borrow(this, moveToMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + + try { + MovementManager.movement(moveToMsg, this); + } catch (MsgSendException e) { + // TODO Auto-generated catch block + Logger.error("Player.MoveTo", this.getName() + " tripped error " + e.getMessage()); + } + + } + + public void updateScaleHeight() { + + float strengthScale = 0; + float unknownScale1 = 0; + float unknownScale2 = 0; + float unknownScale3 = 0; + + float scaleHeight = 0; + + if ((int) this.statStrBase > 40) + strengthScale = ((int) this.statStrBase - 40) * 0.0024999999f; //Y scale ? + + unknownScale1 = (float) (((int) this.statStrBase * 0.0024999999f + strengthScale + 0.89999998) * race.getRaceType().getScaleHeight()); + strengthScale = (int) this.statStrBase * 0.0037499999f + strengthScale + 0.85000002f; //strengthScale is different for x and z + + unknownScale2 = strengthScale * race.getRaceType().getScaleHeight(); //x scale? + unknownScale3 = strengthScale * race.getRaceType().getScaleHeight(); //z Scale? + + + scaleHeight = (1.5f + unknownScale1); + + + this.characterHeight = scaleHeight; + + this.centerHeight = scaleHeight; + + } + + public int getOverFlowEXP() { + return overFlowEXP; + } + + public void setOverFlowEXP(int overFlowEXP) { + this.overFlowEXP = overFlowEXP; + } + + public MovementState getLastMovementState() { + return lastMovementState; + } + + public void setLastMovementState(MovementState lastMovementState) { + this.lastMovementState = lastMovementState; + } + + @Override + public final void setIsCasting(final boolean isCasting) { + if (this.isCasting != isCasting) + this.update(); + this.isCasting = isCasting; + } + + @Override + public void setItemCasting(boolean itemCasting) { + if (this.itemCasting != itemCasting) + this.dynamicUpdate(UpdateType.REGEN); + this.itemCasting = itemCasting; + } + + public void resetRegenUpdateTime() { + this.lastUpdateTime = System.currentTimeMillis(); + this.lastStamUpdateTime = System.currentTimeMillis(); + } + + public float getCharacterHeight() { + return characterHeight; + } + + public void setCharacterHeight(float characterHeight) { + this.characterHeight = characterHeight; + } + + public void setCenterHeight(float centerHeight) { + this.centerHeight = centerHeight; + } + + public boolean isEnteredWorld() { + return enteredWorld; + } + + public void setEnteredWorld(boolean enteredWorld) { + this.enteredWorld = enteredWorld; + } + + public long getChannelMute() { + return channelMute; + } + + public void setChannelMute(long channelMute) { + this.channelMute = channelMute; + } + + public boolean isLastSwimming() { + return lastSwimming; + } + + public boolean isTeleporting() { + return isTeleporting; + } + + public void setTeleporting(boolean isTeleporting) { + this.isTeleporting = isTeleporting; + } + + @Override + public final void teleport(final Vector3fImmutable targetLoc) { + + Regions targetRegion = Regions.GetRegionForTeleport(targetLoc); + locationLock.writeLock().lock(); + + try { + MovementManager.translocate(this, targetLoc, targetRegion); + } catch (Exception e) { + Logger.error(e); + } finally { + locationLock.writeLock().unlock(); + } + } + + public ReadWriteLock getTeleportLock() { + return teleportLock; + } + + public float getBargain() { + float bargain = 0; + + CharacterSkill bargainSkill = this.getSkills().get(engine.Enum.CharacterSkills.Bargaining.name()); + + if (bargainSkill != null) + bargain = bargainSkill.getModifiedAmountBeforeMods(); + + if (bargain > 100) + bargain = 100; + + bargain *= .01f; + + return bargain; + } + + public boolean isDirtyLoad() { + boolean dirtyValue; + dirtyLock.readLock().lock(); + dirtyValue = dirtyLoad; + dirtyLock.readLock().unlock(); + return dirtyValue; + } + + public void setDirtyLoad(boolean dirtyLoad) { + dirtyLock.writeLock().lock(); + this.dirtyLoad = dirtyLoad; + dirtyLock.writeLock().unlock(); + } +} diff --git a/src/engine/objects/PlayerFriends.java b/src/engine/objects/PlayerFriends.java new file mode 100644 index 00000000..97405333 --- /dev/null +++ b/src/engine/objects/PlayerFriends.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.DispatchChannel; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.UpdateFriendStatusMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; + +public class PlayerFriends { + + public static HashMap> PlayerFriendsMap = new HashMap<>(); + public int playerUID; + public int friendUID; + + /** + * ResultSet Constructor + */ + + public PlayerFriends(ResultSet rs) throws SQLException { + this.playerUID = rs.getInt("playerUID"); + this.friendUID = rs.getInt("friendUID"); + + //cache player friends. + //hashset already created, just add to set. + if (PlayerFriendsMap.containsKey(playerUID)) { + HashSet playerFriendSet = PlayerFriendsMap.get(playerUID); + playerFriendSet.add(friendUID); + //hashset not yet created, create new set, and add to map. + } else { + HashSet playerFriendSet = new HashSet<>(); + playerFriendSet.add(friendUID); + PlayerFriendsMap.put(this.playerUID, playerFriendSet); + } + + } + + public PlayerFriends(int playerUID, int friendUID) { + super(); + this.playerUID = playerUID; + this.friendUID = friendUID; + } + + public static void AddToFriends(int playerID, int friendID) { + HashSet friends = PlayerFriendsMap.get(playerID); + + if (friends != null) { + //already in friends list, don't do anything. + if (friends.contains(friendID)) + return; + + DbManager.PlayerCharacterQueries.ADD_FRIEND(playerID, friendID); + friends.add(friendID); + } else { + friends = new HashSet<>(); + DbManager.PlayerCharacterQueries.ADD_FRIEND(playerID, friendID); + friends.add(friendID); + PlayerFriendsMap.put(playerID, friends); + } + } + + public static void RemoveFromFriends(int playerID, int friendID) { + + if (!CanRemove(playerID, friendID)) + return; + + HashSet friends = PlayerFriendsMap.get(playerID); + + if (friends != null) { + DbManager.PlayerCharacterQueries.REMOVE_FRIEND(playerID, friendID); + friends.remove(friendID); + } + } + + public static boolean CanRemove(int playerID, int toRemove) { + if (PlayerFriendsMap.get(playerID) == null) + return false; + + if (PlayerFriendsMap.get(playerID).isEmpty()) + return false; + + if (!PlayerFriendsMap.get(playerID).contains(toRemove)) + return false; + + return true; + } + + public static void SendFriendsStatus(PlayerCharacter player, boolean online) { + HashSet friendsSet = PlayerFriends.PlayerFriendsMap.get(player.getObjectUUID()); + if (friendsSet != null) { + for (int friendID : friendsSet) { + PlayerCharacter friend = SessionManager.getPlayerCharacterByID(friendID); + if (friend == null) + continue; + UpdateFriendStatusMessage outMsg = new UpdateFriendStatusMessage(player); + outMsg.online = online; + Dispatch dispatch = Dispatch.borrow(friend, outMsg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); + } + } + + } + + public int getPlayerUID() { + return playerUID; + } +} diff --git a/src/engine/objects/Portal.java b/src/engine/objects/Portal.java new file mode 100644 index 00000000..f285f45a --- /dev/null +++ b/src/engine/objects/Portal.java @@ -0,0 +1,118 @@ +package engine.objects; + +import engine.Enum; +import engine.Enum.PortalType; +import engine.InterestManagement.WorldGrid; +import engine.gameManager.ConfigManager; +import engine.job.JobScheduler; +import engine.jobs.CloseGateJob; +import engine.math.Vector3fImmutable; +import engine.server.MBServerStatics; + +import java.util.HashSet; + +/* A Runegate object holds an array of these + * portals. This class controls their triggers + * and visual effects. + */ + +public class Portal { + + public final Vector3fImmutable portalLocation; + public Enum.PortalType portalType; + public Building sourceGate; + public Building targetGate; + private boolean active; + private long lastActive = 0; + + public Portal(Building sourceGate, PortalType portalType, Building targetGate) { + Vector3fImmutable tmpLocation; + + this.active = false; + this.sourceGate = sourceGate; + this.targetGate = targetGate; + this.portalType = portalType; + + tmpLocation = sourceGate.getLoc().add(new Vector3fImmutable(portalType.offset.x, 6, portalType.offset.y)); + + // Rotate portal by gate rotation + + tmpLocation = Vector3fImmutable.rotateAroundPoint(sourceGate.getLoc(), tmpLocation, sourceGate.getBounds().getQuaternion().angleY); + + this.portalLocation = tmpLocation; + } + + public boolean isActive() { + + return this.active; + + } + + public void deactivate() { + + // Remove effect bit from the runegate building, which turns off this + // portal type's particle effect + + sourceGate.removeEffectBit(portalType.effectFlag); + this.active = false; + sourceGate.updateEffects(); + } + + public void activate(boolean autoClose) { + + Building sourceBuilding; + + + // Apply effect bit to the runegate building, which turns on this + // portal type's particle effect + + + sourceGate.addEffectBit(portalType.effectFlag); + this.lastActive = System.currentTimeMillis(); + this.active = true; + + // Do not update effects at bootstrap as it + // tries to send a dispatch. + + if (ConfigManager.worldServer.isRunning == true) + sourceGate.updateEffects(); + + if (autoClose == true) { + CloseGateJob cgj = new CloseGateJob(sourceGate, portalType); + JobScheduler.getInstance().scheduleJob(cgj, MBServerStatics.RUNEGATE_CLOSE_TIME); + } + } + + public void collide() { + + HashSet playerList; + + playerList = WorldGrid.getObjectsInRangePartial(this.portalLocation, 2, MBServerStatics.MASK_PLAYER); + + if (playerList.isEmpty()) + return; + + for (AbstractWorldObject player : playerList) { + + onEnter((PlayerCharacter) player); + + } + } + + public void onEnter(PlayerCharacter player) { + + if (player.getTimeStamp("lastMoveGate") < this.lastActive) + return; + + player.teleport(targetGate.getLoc()); + player.setSafeMode(); + + } + + /** + * @return the portalLocation + */ + public Vector3fImmutable getPortalLocation() { + return portalLocation; + } +} diff --git a/src/engine/objects/PowerGrant.java b/src/engine/objects/PowerGrant.java new file mode 100644 index 00000000..cffa21b9 --- /dev/null +++ b/src/engine/objects/PowerGrant.java @@ -0,0 +1,137 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + + +public class PowerGrant extends AbstractGameObject { + + private static ConcurrentHashMap grantedPowers = null; + private int token; + private ConcurrentHashMap runeGrants = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + /** + * ResultSet Constructor + */ + public PowerGrant(ResultSet rs) throws SQLException { + super(); + this.token = rs.getInt("powerToken"); + runeGrants.put(rs.getInt("runeID"), rs.getShort("grantAmount")); + } + + /* + * Getters + */ + + public static Short getGrantedTrains(int token, PlayerCharacter pc) { + if (pc == null) + return (short) 0; + + if (PowerGrant.grantedPowers == null) + fillGrantedPowers(); + + if (PowerGrant.grantedPowers.containsKey(token)) { + PowerGrant pg = PowerGrant.grantedPowers.get(token); + ConcurrentHashMap runeGrants = pg.runeGrants; + ArrayList toks = new ArrayList<>(); + + //get race ID + Race race = pc.getRace(); + if (race != null) + toks.add(race.getRaceRuneID()); + + //get baseClass ID + BaseClass bc = pc.getBaseClass(); + if (bc != null) + toks.add(bc.getObjectUUID()); + + //get promoClass ID + PromotionClass pcc = pc.getPromotionClass(); + if (pcc != null) + toks.add(pcc.getObjectUUID()); + + //get promotion and base class combined ID + if (bc != null && pcc != null) + toks.add(((pcc.getObjectUUID() * 10) + bc.getObjectUUID())); + + //get any other rune IDs + ArrayList runes = pc.getRunes(); + for (CharacterRune rune : runes) + toks.add(rune.getRuneBaseID()); + + //Add any power bonuses granted from runes up + short amount = (short) 0; + for (Integer tok : toks) { + if (runeGrants.containsKey(tok)) + amount += runeGrants.get(tok); + } + + return amount; + } else + return (short) 0; + } + + public static void fillGrantedPowers() { + PowerGrant.grantedPowers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + PreparedStatementShared ps = null; + try { + ps = prepareStatement("SELECT * FROM static_power_powergrant"); + ResultSet rs = ps.executeQuery(); + if (PowerGrant.grantedPowers.size() > 0) { + rs.close(); + return; + } + while (rs.next()) { + int token = rs.getInt("powerToken"); + PowerGrant pg = null; + if (PowerGrant.grantedPowers.containsKey(token)) { + pg = PowerGrant.grantedPowers.get(token); + pg.addRuneGrant(rs.getInt("runeID"), rs.getShort("grantAmount")); + } else { + pg = new PowerGrant(rs); + PowerGrant.grantedPowers.put(token, pg); + } + } + rs.close(); + } catch (SQLException e) { + Logger.error("SQL Error number: " + e.getErrorCode(), e); + } finally { + ps.release(); + } + } + + public ConcurrentHashMap getRuneGrants() { + return this.runeGrants; + } + + + /* + * Database + */ + + public int getToken() { + return this.token; + } + + private void addRuneGrant(int runeID, short amount) { + this.runeGrants.put(runeID, amount); + } + + @Override + public void updateDatabase() { + } +} diff --git a/src/engine/objects/PowerReq.java b/src/engine/objects/PowerReq.java new file mode 100644 index 00000000..bc1e7c2b --- /dev/null +++ b/src/engine/objects/PowerReq.java @@ -0,0 +1,194 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.PowersManager; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; + + +public class PowerReq extends AbstractGameObject implements Comparable { + + private static ArrayList powersForAll = new ArrayList<>(); + private PowersBase powersBase; + private int token; + private short level; + private ConcurrentHashMap powerReqs; + private ConcurrentHashMap skillReqs; + private static ConcurrentHashMap> runePowers = fillRunePowers(); + + /** + * No Table ID Constructor + */ + public PowerReq(PowersBase powersBase, short level, ConcurrentHashMap powerReqs, ConcurrentHashMap skillReqs) { + super(); + this.powersBase = powersBase; + this.level = level; + this.powerReqs = powerReqs; + this.skillReqs = skillReqs; + if (this.powersBase != null) + this.token = this.powersBase.getToken(); + else + this.token = 0; + } + + /** + * Normal Constructor + */ + public PowerReq(PowersBase powersBase, short level, ConcurrentHashMap powerReqs, ConcurrentHashMap skillReqs, int newUUID) { + super(newUUID); + this.powersBase = powersBase; + this.level = level; + this.powerReqs = powerReqs; + this.skillReqs = skillReqs; + if (this.powersBase != null) + this.token = this.powersBase.getToken(); + else + this.token = 0; + } + + /** + * ResultSet Constructor + */ + public PowerReq(ResultSet rs) throws SQLException { + super(rs); + this.token = rs.getInt("powerToken"); + this.powersBase = PowersManager.getPowerByToken(this.token); + this.level = rs.getShort("level"); + int type = rs.getInt("type"); + this.powerReqs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + this.skillReqs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + if (type == 1) + this.skillReqs.put(rs.getInt("requiredToken"), rs.getByte("requiredAmount")); + else if (type == 2) + this.powerReqs.put(rs.getInt("requiredToken"), rs.getByte("requiredAmount")); + } + + public static ArrayList getPowerReqsForRune(int id) { +// if (PowerReq.runePowers == null) +// fillRunePowers(); + if (PowerReq.runePowers.containsKey(id)) + return PowerReq.runePowers.get(id); + return new ArrayList<>(); + } + + public static ConcurrentHashMap> fillRunePowers() { + PowerReq.runePowers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + PreparedStatementShared ps = null; + try { + ps = prepareStatement("SELECT * FROM static_power_powerrequirement"); + ResultSet rs = ps.executeQuery(); + if (PowerReq.runePowers.size() > 0) { + rs.close(); + return PowerReq.runePowers; + } + while (rs.next()) { + ArrayList runePR = null; + int runeID = rs.getInt("runeID"); + int token = rs.getInt("powerToken"); + if (PowerReq.runePowers.containsKey(runeID)) + runePR = PowerReq.runePowers.get(runeID); + else { + runePR = new ArrayList<>(); + PowerReq.runePowers.put(runeID, runePR); + } + boolean found = false; + for (PowerReq pr : runePR) { + if (pr.token == token) { + int type = rs.getInt("type"); + if (type == 1) + pr.addSkill(rs.getInt("requiredToken"), rs.getByte("requiredAmount")); + else + pr.addPower(rs.getInt("requiredToken"), rs.getByte("requiredAmount")); + found = true; + } + } + if (!found) { + PowerReq pr = new PowerReq(rs); + runePR.add(pr); + } + } + rs.close(); + + //order the lists by level so prerequisites are met + for (ArrayList runePR : PowerReq.runePowers.values()) { + Collections.sort(runePR); + } + } catch (SQLException e) { + Logger.error("SQL Error number: " + e.getErrorCode(), e); + } finally { + ps.release(); + } + return PowerReq.runePowers; + } + + /* + * Getters + */ + public PowersBase getPowersBase() { + if (this.powersBase == null) { + this.powersBase = PowersManager.getPowerByToken(this.token); + } + return this.powersBase; + } + + public short getLevel() { + return this.level; + } + + public ConcurrentHashMap getPowerReqs() { + return this.powerReqs; + } + + public ConcurrentHashMap getSkillReqs() { + return this.skillReqs; + } + + public int getToken() { + return this.token; + } + + private void addPower(int token, byte amount) { + this.powerReqs.put(token, amount); + } + + private void addSkill(int token, byte amount) { + this.skillReqs.put(token, amount); + } + + + /* + * Database + */ + + @Override + public int compareTo(PowerReq n) throws ClassCastException { + if (n.level == this.level) + return 0; + else if (this.level > n.level) + return 1; + else + return -1; + } + + @Override + public void updateDatabase() { + + } + + +} \ No newline at end of file diff --git a/src/engine/objects/PowersBaseAttribute.java b/src/engine/objects/PowersBaseAttribute.java new file mode 100644 index 00000000..fa6b5653 --- /dev/null +++ b/src/engine/objects/PowersBaseAttribute.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class PowersBaseAttribute extends AbstractGameObject { + + private final short attributeID; + private final short modValue; + private final short castTime; + private final short duration; + private final short recycleTime; + + /** + * ResultSet Constructor + */ + public PowersBaseAttribute(ResultSet rs) throws SQLException { + super(rs); + + this.attributeID = rs.getShort("attributeID"); + this.modValue = rs.getShort("modValue"); + this.castTime = rs.getShort("castTime"); + this.duration = rs.getShort("duration"); + this.recycleTime = rs.getShort("recycleTime"); + } + + /* + * Getters + */ + public short getAttributeID() { + return attributeID; + } + + public short getModValue() { + return modValue; + } + + public short getCastTime() { + return castTime; + } + + public short getDuration() { + return duration; + } + + public short getRecycleTime() { + return recycleTime; + } + + + @Override + public void updateDatabase() { + // TODO Create update logic. + } +} diff --git a/src/engine/objects/PreparedStatementShared.java b/src/engine/objects/PreparedStatementShared.java new file mode 100644 index 00000000..de3aae0c --- /dev/null +++ b/src/engine/objects/PreparedStatementShared.java @@ -0,0 +1,1261 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; +import engine.job.JobScheduler; +import engine.jobs.BasicScheduledJob; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.*; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A thread-safe sharing implementation of {@link PreparedStatement}. + *

+ * All of the methods from the PreparedStatement interface simply check to see + * that the PreparedStatement is active, and call the corresponding method on + * that PreparedStatement. + * + * @author Burfo + * @see PreparedStatement + **/ +public class PreparedStatementShared implements PreparedStatement { + private static final ConcurrentHashMap> statementList = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private static final ArrayList statementListDelegated = new ArrayList<>(); + private static final String ExceptionMessage = "PreparedStatementShared object " + "was accessed after being released."; + private static boolean debuggingIsOn; + + private PreparedStatement ps = null; + private int sqlHash; + private String sql; + private long delegatedTime; + + //debugging variables + private StackTraceElement[] stackTrace; + private DebugParam[] variables; + private String filteredSql; + + /** + * Generates a new PreparedStatementShared based on the specified sql. + * + * @param sql Query string to generate the PreparedStatement + * @throws SQLException + **/ + public PreparedStatementShared(String sql) throws SQLException { + this.sqlHash = sql.hashCode(); + this.sql = sql; + this.delegatedTime = System.currentTimeMillis(); + this.ps = getFromPool(sql, sqlHash); + if (this.ps == null) { + this.ps = createNew(sql, sqlHash); + } + + + if (debuggingIsOn) { + //see if there are any '?' in the statement that are not bind variables + //and filter them out. + boolean isString = false; + char[] sqlString = this.sql.toCharArray(); + for (int i = 0; i < sqlString.length; i++) { + if (sqlString[i] == '\'') + isString = !isString; + //substitute the ? with an unprintable character if is in a string + if (sqlString[i] == '?' && isString) + sqlString[i] = '\u0007'; + } + this.filteredSql = new String(sqlString); + + //find out how many variables are present in statement. + int count = 0; + int index = -1; + while ((index = filteredSql.indexOf('?', index + 1)) != -1) { + count++; + } + + //create variables array with size equal to count of variables + this.variables = new DebugParam[count]; + + this.stackTrace = Thread.currentThread().getStackTrace(); + + } else { + this.stackTrace = null; + this.variables = null; + this.filteredSql = null; + } + + synchronized (statementListDelegated) { + statementListDelegated.add(this); + } + + } + + private static PreparedStatement getFromPool(String sql, int sqlHash) throws SQLException { + PreparedStatement ps = null; + + if (statementList.containsKey(sqlHash)) { + LinkedList list = statementList.get(sqlHash); + if (list == null) { // Shouldn't happen b/c no keys are ever removed + throw new AssertionError("list cannot be null."); + } + boolean success = false; + synchronized (list) { + do { + ps = list.pollFirst(); + if (ps == null) { + break; + } + if (ps.isClosed()) { // should rarely happen + Logger.warn("A closed PreparedStatement was removed " + + "from AbstractGameObject statementList. " + "SQL: " + sql); + } else { + success = true; + } + } while (!success); + } + + if (ps != null) { + if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) { + Logger.info("Found cached PreparedStatement for SQL hash: " + sqlHash + + " SQL String: " + sql); + } + } + } + return ps; + } + + private static PreparedStatement createNew(String sql, int sqlHash) throws SQLException { + statementList.putIfAbsent(sqlHash, new LinkedList<>()); + return DbManager.prepareStatement(sql); + } + + public static void submitPreparedStatementsCleaningJob() { + JobScheduler.getInstance().scheduleJob(new BasicScheduledJob("cleanUnreleasedStatements", PreparedStatementShared.class), 1000 * 60 * 2); // 2 + // minutes + } + + public static void cleanUnreleasedStatements() { + long now = System.currentTimeMillis(); + long timeLimit = 120000; // 2 minutes + + synchronized (statementListDelegated) { + Iterator iterator = statementListDelegated.iterator(); + while (iterator.hasNext()) { + PreparedStatementShared pss = iterator.next(); + if ((pss.delegatedTime + timeLimit) >= now) { + continue; + } + iterator.remove(); + + Logger.warn("Forcefully released after being held for > 2 minutes." + " SQL STRING: \"" + + pss.sql + "\" METHOD: " + pss.getTraceInfo()); + } + } + + submitPreparedStatementsCleaningJob(); // resubmit + } + + public static void enableDebugging() { + debuggingIsOn = true; + Logger.info("Database debugging has been enabled."); + } + + public static void disableDebugging() { + debuggingIsOn = false; + Logger.info("Database debugging has been disabled."); + } + + @Override + public boolean isCloseOnCompletion() { + return true; + } + + @Override + public void closeOnCompletion() { + Logger.warn("Prepared Statement Closed"); + } + + /** + * Releases the use of a PreparedStatementShared that was generated by + * {@link AbstractGameObject#prepareStatement}, making it available for use + * by another query. + *

+ * Do not utilize or modify the object after calling this method. + *

+ * Example: + * + *

+     * @code
+     * PreparedStatementShared ps = prepareStatement(...);
+     * ps.executeUpdate();
+     * ps.release();
+     * ps = null;}
+     * 
+ **/ + public void release() { + if (this.ps == null) { + return; + } // nothing to release + if (statementListDelegated.contains(this)) { + synchronized (statementListDelegated) { + statementListDelegated.remove(this); + } + try { + if (this.ps.isClosed()) { + return; + } + this.ps.clearParameters(); + this.variables = null; + } catch (SQLException ignore) { + } + + // add back to pool + LinkedList list = statementList.get(this.sqlHash); + if (list == null) { + return; + } + synchronized (list) { + list.add(this.ps); + } + } + // clear values from this object so caller cannot use it after it has + // been released + this.ps = null; + this.sqlHash = 0; + this.sql = ""; + this.delegatedTime = 0; + this.stackTrace = null; + } + + /** + * Determines if the object is in a usable state. + * + * @return True if the object is in a useable state. + **/ + public boolean isUsable() { + if (ps == null) { + return false; + } + try { + if (ps.isClosed()) { + return false; + } + } catch (SQLException e) { + return false; + } + return true; + } + + private String getTraceInfo() { + if (stackTrace == null) { + return ""; + } + + if (stackTrace.length > 3) { + return stackTrace[3].getClassName() + '.' + stackTrace[3].getMethodName(); + } else if (stackTrace.length == 0) { + return ""; + } else { + return stackTrace[stackTrace.length - 1].getClassName() + '.' + stackTrace[stackTrace.length - 1].getMethodName(); + } + } + + @Override + public boolean equals(Object obj) { + if (ps == null || obj == null) { + return false; + } + + if (obj instanceof PreparedStatementShared) { + return this.ps.equals(((PreparedStatementShared) obj).ps); + } + + if (obj instanceof PreparedStatement) { + return this.ps.equals(obj); + } + + return false; + } + + @Override + public String toString() { + if (!debuggingIsOn || variables == null) { + return "SQL: " + this.sql + " (enable DB debugging for more data)"; + } + + String out; + + out = "SQL: [" + this.sql + "] "; + out += "VARIABLES[count=" + variables.length + "]: "; + + for (int i = 0; i < variables.length; i++) { + out += "[" + (i + 1) + "]: "; + DebugParam dp = variables[i]; + if (dp == null || !dp.isValueAssigned()) { + out += "{MISSING} "; + continue; + } + Object dpObj = dp.getDebugObject(); + out += dpObj.toString() + ' '; + } + return out; + } + + @Override + public void addBatch() throws SQLException { + if (this.ps == null) { + throw new SQLException(); + } + this.ps.addBatch(); + + } + + private void saveObject(int parameterIndex, Object obj) throws SQLException { + if (!debuggingIsOn || this.variables == null) { + return; + } + + if (parameterIndex > variables.length) { + throw new SQLException("Parameter index of " + parameterIndex + + " exceeds actual parameter count of " + this.variables.length); + } + + this.variables[parameterIndex - 1] = new DebugParam(obj); + } + + private void logExceptionAndRethrow(SQLException e) throws SQLException { + Logger.error("SQL operation failed: (" + + e.getMessage() + ") " + this.toString(), e); + throw e; + } + + @Override + public void clearParameters() throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + + this.ps.clearParameters(); + for (int i = 0; i < this.variables.length; i++) { + this.variables[i] = null; + } + + } + + @Override + public boolean execute() throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + + if (debuggingIsOn || MBServerStatics.ENABLE_EXECUTION_TIME_WARNING) { + long startTime = System.currentTimeMillis(); + boolean rs = false; + try { + rs = this.ps.execute(); + } catch (SQLException e) { + logExceptionAndRethrow(e); + } + if ((startTime + MBServerStatics.DB_EXECUTION_WARNING_TIME_MS) < System.currentTimeMillis()) + Logger.warn("The following statement took " + (System.currentTimeMillis() - startTime) + + " millis to execute: " + this.sql); + return rs; + } + + return this.ps.execute(); + } + + @Override + public ResultSet executeQuery() throws SQLException, SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + + if (debuggingIsOn || MBServerStatics.ENABLE_QUERY_TIME_WARNING) { + long startTime = System.currentTimeMillis(); + ResultSet rs = null; + try { + rs = this.ps.executeQuery(); + } catch (SQLException e) { + logExceptionAndRethrow(e); + } + if ((startTime + MBServerStatics.DB_QUERY_WARNING_TIME_MS) < System.currentTimeMillis()) + Logger.warn("The following query took " + (System.currentTimeMillis() - startTime) + + " millis to execute: " + this.sql); + return rs; + } + + return this.ps.executeQuery(); + } + + @Override + public int executeUpdate() throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + + if (debuggingIsOn || MBServerStatics.ENABLE_UPDATE_TIME_WARNING) { + long startTime = System.currentTimeMillis(); + int rs = 0; + try { + rs = this.ps.executeUpdate(); + } catch (SQLException e) { + logExceptionAndRethrow(e); + } + if ((startTime + MBServerStatics.DB_UPDATE_WARNING_TIME_MS) < System.currentTimeMillis()) + Logger.warn("The following update took " + (System.currentTimeMillis() - startTime) + + " millis to execute: " + this.sql); + return rs; + } + + return this.ps.executeUpdate(); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + return this.ps.getMetaData(); + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + return this.ps.getParameterMetaData(); + } + + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + this.saveObject(parameterIndex, x); + this.ps.setArray(parameterIndex, x); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (x == null ? "NULL" : "")); + this.ps.setAsciiStream(parameterIndex, x); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (x == null ? "NULL" : "")); + this.ps.setBinaryStream(parameterIndex, x); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (x == null ? "NULL" : "")); + this.ps.setBlob(parameterIndex, x); + } + + @Override + public void setBlob(int parameterIndex, InputStream x, long length) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (x == null ? "NULL" : "")); + this.ps.setCharacterStream(parameterIndex, reader); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (reader == null ? "NULL" : "")); + this.ps.setClob(parameterIndex, reader); + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (reader == null ? "NULL" : "")); + this.ps.setNCharacterStream(parameterIndex, value); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (value == null ? "NULL" : "")); + this.ps.setNClob(parameterIndex, value); + } + + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (reader == null ? "NULL" : "")); + this.ps.setNClob(parameterIndex, reader); + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + saveObject(parameterIndex, (reader == null ? "NULL" : " iface) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + return this.ps.isWrapperFor(iface); + } + + @Override + public T unwrap(Class iface) throws SQLException { + if (this.ps == null) { + throw new SQLException(ExceptionMessage); + } + return this.ps.unwrap(iface); + } + + private class DebugParam { + private Object debugObject; + private boolean valueAssigned; + + public DebugParam(Object debugObject) { + this.debugObject = debugObject; + valueAssigned = true; + } + + public Object getDebugObject() { + return debugObject; + } + + public boolean isValueAssigned() { + return valueAssigned; + } + } + +} diff --git a/src/engine/objects/ProducedItem.java b/src/engine/objects/ProducedItem.java new file mode 100644 index 00000000..3e3caafb --- /dev/null +++ b/src/engine/objects/ProducedItem.java @@ -0,0 +1,233 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.DispatchChannel; +import engine.gameManager.PowersManager; +import engine.net.DispatchMessage; +import engine.net.client.msg.ItemProductionMsg; +import engine.powers.EffectsBase; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Date; + +public class ProducedItem { + + private int ID; + private int npcUID; + private int itemBaseID; + private DateTime dateToUpgrade; + private boolean isRandom; + + private String prefix; + private String suffix; + private String name; + private int amount; + private int producedItemID = 0; + private boolean inForge; + private int value; + + private int playerID; + + + /** + * ResultSet Constructor + */ + + public ProducedItem(ResultSet rs) throws SQLException { + this.ID = rs.getInt("ID"); + this.npcUID = rs.getInt("npcUID"); + this.itemBaseID = rs.getInt("itemBaseID"); + + Date sqlDateTime = rs.getTimestamp("dateToUpgrade"); + + if (sqlDateTime != null) + this.dateToUpgrade = new DateTime(sqlDateTime); + else + dateToUpgrade = null; + this.isRandom = rs.getBoolean("isRandom"); + this.prefix = rs.getString("prefix"); + this.suffix = rs.getString("suffix"); + this.name = rs.getString("name"); + this.inForge = rs.getBoolean("inForge"); + this.value = rs.getInt("value"); + this.playerID = rs.getInt("playerID"); + } + + public ProducedItem(int ID, int npcUID, int itemBaseID, DateTime dateToUpgrade, boolean isRandom, String prefix, String suffix, String name, int playerID) { + super(); + this.ID = ID; + this.npcUID = npcUID; + this.itemBaseID = itemBaseID; + this.dateToUpgrade = dateToUpgrade; + this.isRandom = isRandom; + this.prefix = prefix; + this.suffix = suffix; + this.name = name; + this.value = 0; + this.playerID = playerID; + + + } + + public int getNpcUID() { + return npcUID; + } + + public DateTime getDateToUpgrade() { + return dateToUpgrade; + } + + public int getItemBaseID() { + return itemBaseID; + } + + public void setItemBaseID(int itemBaseID) { + this.itemBaseID = itemBaseID; + } + + public boolean isRandom() { + return isRandom; + } + + public void setRandom(boolean isRandom) { + this.isRandom = isRandom; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getID() { + return ID; + } + + public void setID(int iD) { + ID = iD; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + + public int getProducedItemID() { + return producedItemID; + } + + public void setProducedItemID(int producedItemID) { + this.producedItemID = producedItemID; + } + + public boolean isInForge() { + return inForge; + } + + public void setInForge(boolean inForge) { + this.inForge = inForge; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public boolean finishProduction() { + NPC npc = NPC.getFromCache(this.getNpcUID()); + + if (npc == null) + return false; + + + //update the client to ID the item in the window when item finishes rolling. + //If there is more than 1 item left to roll, complete the item and throw it in inventory + //and reproduce next item. + + try { + + if (this.getAmount() > 1) { + this.setAmount(this.getAmount() - 1); + npc.completeItem(this.getProducedItemID()); + + int pToken = 0; + int sToken = 0; + + if (!this.isRandom()) { + EffectsBase eb = PowersManager.getEffectByIDString(this.getPrefix()); + if (eb != null) + pToken = eb.getToken(); + eb = PowersManager.getEffectByIDString(this.getSuffix()); + if (eb != null) + sToken = eb.getToken(); + + } + + Item item = npc.produceItem(0, this.getAmount(), this.isRandom(), pToken, sToken, this.getName(), this.getItemBaseID()); + + if (item == null) + return false; + + } else { + + //update item to complete + MobLoot targetItem = MobLoot.getFromCache(this.getProducedItemID()); + + if (targetItem == null) + return false; + + ItemProductionMsg outMsg = new ItemProductionMsg(npc.getBuilding(), npc, targetItem, 8, true); + + + DispatchMessage.dispatchMsgToInterestArea(npc, outMsg, DispatchChannel.SECONDARY, 700, false, false); + } + + } catch (Exception e) { + Logger.error(e); + return false; + } + return true; + } + + public int getPlayerID() { + return playerID; + } + + +} diff --git a/src/engine/objects/PromotionClass.java b/src/engine/objects/PromotionClass.java new file mode 100644 index 00000000..08e476f3 --- /dev/null +++ b/src/engine/objects/PromotionClass.java @@ -0,0 +1,201 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + + +public class PromotionClass extends AbstractGameObject { + + private final String name; + private final String description; + private final float healthMod; + private final float manaMod; + private final float staminaMod; + private final ArrayList allowedRunes; + private final ArrayList skillsGranted; + private final ArrayList powersGranted; + private final ArrayList effectsGranted; + private final ArrayList effectsGrantedFighter; + private final ArrayList effectsGrantedHealer; + private final ArrayList effectsGrantedRogue; + private final ArrayList effectsGrantedMage; + private int token = 0; + private ArrayList effectsList = new ArrayList<>(); + + /** + * No Table ID Constructor + */ + public PromotionClass(String name, String description, + ArrayList allowedRunes, ArrayList skillsGranted, ArrayList powersGranted) { + super(); + this.name = name; + this.description = description; + this.allowedRunes = allowedRunes; + this.skillsGranted = skillsGranted; + this.powersGranted = powersGranted; + this.healthMod = 0f; + this.manaMod = 0f; + this.staminaMod = 0f; + this.effectsGranted = new ArrayList<>(); + this.effectsGrantedFighter = new ArrayList<>(); + this.effectsGrantedHealer = new ArrayList<>(); + this.effectsGrantedRogue = new ArrayList<>(); + this.effectsGrantedMage = new ArrayList<>(); + } + + /** + * Normal Constructor + */ + public PromotionClass(String name, String description, + ArrayList allowedRunes, ArrayList skillsGranted, ArrayList powersGranted, int newUUID) { + super(newUUID); + this.name = name; + this.description = description; + this.allowedRunes = allowedRunes; + this.skillsGranted = skillsGranted; + this.powersGranted = powersGranted; + this.healthMod = 0f; + this.manaMod = 0f; + this.staminaMod = 0f; + this.effectsGranted = new ArrayList<>(); + this.effectsGrantedFighter = new ArrayList<>(); + this.effectsGrantedHealer = new ArrayList<>(); + this.effectsGrantedRogue = new ArrayList<>(); + this.effectsGrantedMage = new ArrayList<>(); + } + + /** + * ResultSet Constructor + */ + public PromotionClass(ResultSet rs) throws SQLException { + super(rs); + + this.name = rs.getString("name"); + this.description = rs.getString("description"); + this.token = rs.getInt("token"); + this.healthMod = rs.getFloat("healthMod"); + this.manaMod = rs.getFloat("manaMod"); + this.staminaMod = rs.getFloat("staminaMod"); + this.allowedRunes = DbManager.PromotionQueries.GET_ALLOWED_RUNES(this); + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID()); + this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID()); + this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.getObjectUUID()); + this.effectsGrantedFighter = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2500); + this.effectsGrantedHealer = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2501); + this.effectsGrantedRogue = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2502); + this.effectsGrantedMage = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2503); + this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); + + + } + + public static void serializeForClientMsg(PromotionClass promotionClass, ByteBufferWriter writer) { + writer.putInt(3); // For BaseClass + writer.putInt(0); // Pad + writer.putInt(promotionClass.getObjectUUID()); + writer.putInt(promotionClass.getObjectType().ordinal()); + writer.putInt(promotionClass.getObjectUUID()); + } + + public static PromotionClass GetPromtionClassFromCache(int runeID) { + if (runeID == 0) + return null; + return (PromotionClass) DbManager.getFromCache(GameObjectType.PromotionClass, runeID); + } + + /* + * Getters + */ + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public int getToken() { + return this.token; + } + + public float getHealthMod() { + return this.healthMod; + } + + public float getManaMod() { + return this.manaMod; + } + + public float getStaminaMod() { + return this.staminaMod; + } + + public boolean isAllowedRune(int token) { + for (int b : this.allowedRunes) { + if (token == b) { + return true; + } + } + return false; + } + + public ArrayList getRuneList() { + return this.allowedRunes; + } + + public ArrayList getSkillsGranted() { + return this.skillsGranted; + } + + public ArrayList getPowersGranted() { + return this.powersGranted; + } + + /* + * Serializing + */ + + public ArrayList getEffectsGranted() { + return this.effectsGranted; + } + + public ArrayList getEffectsGranted(int baseClassID) { + if (baseClassID == 2500) + return this.effectsGrantedFighter; + else if (baseClassID == 2501) + return this.effectsGrantedHealer; + else if (baseClassID == 2502) + return this.effectsGrantedRogue; + else if (baseClassID == 2503) + return this.effectsGrantedMage; + else + return new ArrayList<>(); + } + + @Override + public void updateDatabase() { + // TODO Create update logic. + } + + public ArrayList getEffectsList() { + return effectsList; + } + + public void setEffectsList(ArrayList effectsList) { + this.effectsList = effectsList; + } +} diff --git a/src/engine/objects/Race.java b/src/engine/objects/Race.java new file mode 100644 index 00000000..93ef9426 --- /dev/null +++ b/src/engine/objects/Race.java @@ -0,0 +1,354 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.RaceType; +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +public class Race { + + // Local class cache + + private static ConcurrentHashMap _raceByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + private final String name; + private final String description; + private final int raceRuneID; + private final short strStart; + private final short strMin; + private final short strMax; + private final short dexStart; + private final short dexMin; + private final short dexMax; + private final short conStart; + private final short conMin; + private final short conMax; + private final short intStart; + private final short intMin; + private final short intMax; + private final short spiStart; + private final short spiMin; + private final short spiMax; + private final short healthBonus; + private final short manaBonus; + private final short staminaBonus; + private final byte startingPoints; + private final float minHeight; + private final float strHeightMod; + private final HashSet skinColors; + private final HashSet beardColors; + private final HashSet hairColors; + private final ArrayList baseClasses; + private final ArrayList skillsGranted; + private final ArrayList powersGranted; + private Enum.RaceType raceType; + private int token = 0; + private HashSet hairStyles; + private HashSet beardStyles; + private ArrayList effectsList = new ArrayList<>(); + + /** + * ResultSet Constructor + */ + public Race(ResultSet rs) throws SQLException { + + this.raceRuneID = rs.getInt("ID"); + this.raceType = Enum.RaceType.getRaceTypebyRuneID(raceRuneID); + this.name = rs.getString("name"); + this.description = rs.getString("description"); + this.strStart = rs.getShort("strStart"); + this.strMin = rs.getShort("strMin"); + this.strMax = rs.getShort("strMax"); + this.dexStart = rs.getShort("dexStart"); + this.dexMin = rs.getShort("dexMin"); + this.dexMax = rs.getShort("dexMax"); + this.conStart = rs.getShort("conStart"); + this.conMin = rs.getShort("conMin"); + this.conMax = rs.getShort("conMax"); + this.intStart = rs.getShort("intStart"); + this.intMin = rs.getShort("intMin"); + this.intMax = rs.getShort("intMax"); + this.spiStart = rs.getShort("spiStart"); + this.spiMin = rs.getShort("spiMin"); + this.spiMax = rs.getShort("spiMax"); + this.token = rs.getInt("token"); + this.healthBonus = rs.getShort("healthBonus"); + this.manaBonus = rs.getShort("manaBonus"); + this.staminaBonus = rs.getShort("staminaBonus"); + this.startingPoints = rs.getByte("startingPoints"); + this.raceType = RaceType.getRaceTypebyRuneID(this.raceRuneID); + this.minHeight = rs.getFloat("minHeight"); + this.strHeightMod = rs.getFloat("strHeightMod"); + this.hairStyles = DbManager.RaceQueries.HAIR_STYLES_FOR_RACE(raceRuneID); + this.beardStyles = DbManager.RaceQueries.BEARD_STYLES_FOR_RACE(raceRuneID); + this.skinColors = DbManager.RaceQueries.SKIN_COLOR_FOR_RACE(raceRuneID); + this.beardColors = DbManager.RaceQueries.BEARD_COLORS_FOR_RACE(raceRuneID); + this.hairColors = DbManager.RaceQueries.HAIR_COLORS_FOR_RACE(raceRuneID); + this.baseClasses = DbManager.BaseClassQueries.GET_BASECLASS_FOR_RACE(raceRuneID); + this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(raceRuneID); + this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID); + this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.raceRuneID); + + } + + public static void loadAllRaces() { + Race._raceByID = DbManager.RaceQueries.LOAD_ALL_RACES(); + } + + public static Race getRace(int id) { + return _raceByID.get(id); + } + + @Override + public boolean equals(Object object) { + + if ((object instanceof Race) == false) + return false; + + Race race = (Race) object; + + return this.raceRuneID == race.raceRuneID; + } + + @Override + public int hashCode() { + return this.raceRuneID; + } + + /* + * Getters + */ + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public short getStrStart() { + return strStart; + } + + public short getStrMin() { + return strMin; + } + + public short getStrMax() { + return strMax; + } + + public short getDexStart() { + return dexStart; + } + + public short getDexMin() { + return dexMin; + } + + public short getDexMax() { + return dexMax; + } + + public short getConStart() { + return conStart; + } + + public short getConMin() { + return conMin; + } + + public short getConMax() { + return conMax; + } + + public short getIntStart() { + return intStart; + } + + public short getIntMin() { + return intMin; + } + + public short getIntMax() { + return intMax; + } + + public short getSpiStart() { + return spiStart; + } + + public short getSpiMin() { + return spiMin; + } + + public short getSpiMax() { + return spiMax; + } + + public byte getStartingPoints() { + return startingPoints; + } + + public int getToken() { + return token; + } + + public final HashSet getHairStyles() { + return hairStyles; + } + + public final HashSet getBeardStyles() { + return beardStyles; + } + + public final HashSet getBeardColors() { + return beardColors; + } + + public HashSet getHairColors() { + return hairColors; + } + + public HashSet getSkinColors() { + return skinColors; + } + + public int getNumSkinColors() { + return this.skinColors.size(); + } + + public int getNumHairColors() { + return this.hairColors.size(); + } + + public int getNumBeardColors() { + return this.beardColors.size(); + } + + public final ArrayList getValidBaseClasses() { + return baseClasses; + } + + public ArrayList getAllowedRunes() { + return RuneBase.AllowedRaceRunesMap.get(raceRuneID); + } + + public ArrayList getSkillsGranted() { + return this.skillsGranted; + } + + public ArrayList getPowersGranted() { + return this.powersGranted; + } + + public ArrayList getEffectsGranted() { + return RuneBaseEffect.RuneIDBaseEffectMap.get(this.raceRuneID); + } + + /* + * Validators + */ + public boolean isValidBeardStyle(int id) { + return this.beardStyles.contains(id); + } + + public boolean isValidBeardColor(int id) { + return this.beardColors.contains(id); + } + + public boolean isValidHairStyle(int id) { + return this.hairStyles.contains(id); + } + + public boolean isValidHairColor(int id) { + return this.hairColors.contains(id); + } + + public boolean isValidSkinColor(int id) { + return this.skinColors.contains(id); + } + + public boolean isAllowedRune(RuneBase rb) { + + if (this.getAllowedRunes() != null) + if (this.getAllowedRunes().contains(rb.getObjectUUID())) + return true; + if (RuneBase.AllowedBaseClassRunesMap.containsKey(111111)) { + if (RuneBase.AllowedRaceRunesMap.get(111111).contains(rb.getObjectUUID())) + return true; + } + return false; + } + + public float getHealthBonus() { + return this.healthBonus; + } + + public float getManaBonus() { + return this.manaBonus; + } + + public float getStaminaBonus() { + return this.staminaBonus; + } + + public float getMinHeight() { + return this.minHeight; + } + + public float getStrHeightMod() { + return this.strHeightMod; + } + + public float getHeight(short str) { + return this.minHeight + (this.strHeightMod * str); + } + + + /* + * Serializing + */ + + public float getCenterHeight(short str) { + return getHeight(str) / 2f; + } + + public void serializeForClientMsg(ByteBufferWriter writer) { + writer.putInt(1); // For Race + writer.putInt(0); // Pad + writer.putInt(this.raceRuneID); + + writer.putInt(Enum.GameObjectType.Race.ordinal()); + writer.putInt(raceRuneID); + } + + public int getRaceRuneID() { + return raceRuneID; + } + + public Enum.RaceType getRaceType() { + return raceType; + } + + + public ArrayList getEffectsList() { + return effectsList; + } +} diff --git a/src/engine/objects/Realm.java b/src/engine/objects/Realm.java new file mode 100644 index 00000000..e97a4533 --- /dev/null +++ b/src/engine/objects/Realm.java @@ -0,0 +1,448 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.InterestManagement.RealmMap; +import engine.db.archive.DataWarehouse; +import engine.db.archive.RealmRecord; +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.net.ByteBufferWriter; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.awt.*; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +import static engine.Enum.CharterType; + + +public class Realm { + + // Internal class cache + + public static ConcurrentHashMap _realms = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public final int realmID; + private final float mapR; //Red color + private final float mapG; //Green color + private final float mapB; //Blue color + private final float mapA; //Alpha color + private final boolean canBeClaimed; + private final boolean canPlaceCities; + private final int numCities; + private final String realmName; + private final float mapY1; + private final float mapX1; + private final float mapY2; + private final float mapX2; + private final int stretchX; + private final int stretchY; + private final int locX; + private final int locY; + private final HashSet cities = new HashSet<>(); + public Color mapColor; + public LocalDateTime ruledSince; + private int rulingCityUUID; + private int rulingCharacterUUID; + private int rulingCharacterOrdinal; + private String rulingCharacterName; + private int rulingNationUUID; + private GuildTag rulingNationTags; + private String rulingNationName; + private int charterType; + private String hash; + + /** + * ResultSet Constructor + */ + public Realm(ResultSet rs) throws SQLException { + + this.mapColor = new Color(Integer.parseInt(rs.getString("realmColor"), 16)); + this.mapR = (float) (mapColor.getRed() * 0.00392156863); + this.mapG = (float) (mapColor.getGreen() * 0.00392156863); + this.mapB = (float) (mapColor.getBlue() * 0.00392156863); + this.mapA = 1; + this.canBeClaimed = rs.getBoolean("canBeClaimed"); + this.canPlaceCities = rs.getBoolean("canPlaceCities"); + this.numCities = rs.getInt("numCities"); + this.realmName = rs.getString("realmName"); + this.rulingCityUUID = rs.getInt("rulingCityUID"); + this.charterType = rs.getInt("charterType"); + + java.sql.Timestamp ruledTimeStamp = rs.getTimestamp("ruledSince"); + + if (ruledTimeStamp != null) + this.ruledSince = LocalDateTime.ofInstant(ruledTimeStamp.toInstant(), ZoneId.systemDefault()); + + this.mapY1 = rs.getFloat("mapY1"); + this.mapX1 = rs.getFloat("mapX1"); + this.mapY2 = rs.getFloat("mapY2"); + this.mapX2 = rs.getFloat("mapX2"); + this.stretchX = rs.getInt("stretchX"); + this.stretchY = rs.getInt("stretchY"); + this.locX = rs.getInt("locX"); + this.locY = rs.getInt("locY"); + this.realmID = rs.getInt("realmID"); + this.hash = rs.getString("hash"); + } + + public static void configureAllRealms() { + + for (Realm realm : Realm._realms.values()) { + realm.configure(); + } + } + + public static Realm getRealm(int realmID) { + return _realms.get(realmID); + } + + public static boolean HasAllBlessings(PlayerCharacter claimer) { + + if (claimer == null) + return false; + + PowersBase powerBlessing = PowersManager.getPowerByIDString("BLS-POWER"); + if (!claimer.effects.containsKey(Integer.toString(powerBlessing.getActions().get(0).getUUID()))) + return false; + PowersBase wisdomBlessing = PowersManager.getPowerByIDString("BLS-POWER"); + if (!claimer.effects.containsKey(Integer.toString(wisdomBlessing.getActions().get(0).getUUID()))) + return false; + PowersBase fortuneBlessing = PowersManager.getPowerByIDString("BLS-FORTUNE"); + return claimer.effects.containsKey(Integer.toString(fortuneBlessing.getActions().get(0).getUUID())); + } + + public static float getRealmHealthMod(City city) { + Realm serverRealm; + int charterType; + float returnBonus = 0.0f; + + serverRealm = RealmMap.getRealmForCity(city); + charterType = serverRealm.charterType; + + switch (charterType) { + + case 762228431: + returnBonus = 0.0f; + break; + case -15978914: + returnBonus = -.15f; + break; + case -600065291: + returnBonus = .15f; + break; + default: + break; + } + + return returnBonus; + } + + public static int getRealmMesh(City city) { + Realm serverRealm; + CharterType charterType; + + serverRealm = city.getRealm(); + charterType = CharterType.getCharterTypeByID(serverRealm.charterType); + + return charterType.getMeshID(); + } + + public static void loadAllRealms() { + + _realms = DbManager.RealmQueries.LOAD_ALL_REALMS(); + + } + + /* + * Getters + */ + public boolean isRuled() { + return (this.rulingCityUUID != 0); + } + + public float getMapR() { + return this.mapR; + } + + public float getMapG() { + return this.mapG; + } + + public float getMapB() { + return this.mapB; + } + + public float getMapA() { + return this.mapA; + } + + public boolean getCanBeClaimed() { + return this.canBeClaimed; + } + + public boolean getCanPlaceCities() { + return this.canPlaceCities; + } + + public int getNumCities() { + return this.numCities; + } + + public String getRealmName() { + return this.realmName; + } + + public City getRulingCity() { + return City.getCity(this.rulingCityUUID); + } + + public float getMapY1() { + return this.mapY1; + } + + public float getMapX1() { + return this.mapX1; + } + + public float getMapY2() { + return this.mapY2; + } + + public float getMapX2() { + return this.mapX2; + } + + public int getStretchX() { + return this.stretchX; + } + + public int getStretchY() { + return this.stretchY; + } + + public int getlocX() { + return this.locX; + } + + public int getlocY() { + return this.locY; + } + + // Call this after changing ownership before you serialize a realm + + public int getRealmID() { + return this.realmID; + } + + public void addCity(int cityUUID) { + if (!this.cities.add(cityUUID)) + this.cities.add(cityUUID); + } + + public void removeCity(int cityUUID) { + this.cities.remove(cityUUID); + } + + public boolean isRealmFull() { + + return this.cities.size() >= this.numCities; + } + + public boolean isRealmFullAfterBane() { + + return this.cities.size() > this.numCities; + } + + public void configure() { + + PlayerCharacter rulingCharacter; + + // Configure what exactly? We won't send any of it. + + if (this.rulingCityUUID == 0) + return; + if (this.getRulingCity() == null) + return; + if (this.getRulingCity().getTOL() == null) + return; + + rulingCharacter = PlayerCharacter.getPlayerCharacter(this.getRulingCity().getTOL().getOwnerUUID()); + if (rulingCharacter == null) { + Logger.info(this.realmName + " failed to load " + this.getRulingCity().getCityName() + " ID : " + this.rulingCityUUID); + return; + } + + this.rulingCharacterUUID = rulingCharacter.getObjectUUID(); + this.rulingCharacterOrdinal = rulingCharacter.getObjectType().ordinal(); + this.rulingCharacterName = rulingCharacter.getFirstName() + ' ' + rulingCharacter.getLastName(); + this.rulingNationUUID = rulingCharacter.getGuild().getNation().getObjectUUID(); + this.rulingNationName = rulingCharacter.getGuild().getNation().getName(); + this.rulingNationTags = rulingCharacter.getGuild().getNation().getGuildTag(); + } + + public void serializeForClientMsg(ByteBufferWriter writer) { + + writer.putFloat(this.mapR); + writer.putFloat(this.mapG); + writer.putFloat(this.mapB); + writer.putFloat(this.mapA); + writer.put((byte) (this.canBeClaimed ? 0x1 : 0x0)); + writer.put((byte) (this.canPlaceCities ? 0x1 : 0x0)); + writer.putInt(this.numCities); + writer.putFloat(this.mapR); + writer.putFloat(this.mapG); + writer.putFloat(this.mapB); + writer.putFloat(this.mapA); + writer.putString(this.realmName); + + if (isRuled() == true) { + writer.putInt(Enum.GameObjectType.Guild.ordinal()); + writer.putInt(rulingNationUUID); + + writer.putInt(rulingCharacterOrdinal); + writer.putInt(rulingCharacterUUID); + + writer.putInt(Enum.GameObjectType.City.ordinal()); + writer.putInt(rulingCityUUID); + + writer.putLocalDateTime(this.ruledSince); + + writer.putString(rulingNationName); + GuildTag._serializeForDisplay(rulingNationTags, writer); + writer.putString(rulingCharacterName); + writer.putInt(0xB); // Display Title: enum index starts at 10. + } else { + if (this.rulingCityUUID != 0) + Logger.error("Failed to Load realm info for city" + this.rulingCityUUID); + writer.putLong(0); + writer.putLong(0); + writer.putLong(0); + writer.put((byte) 1); + writer.put((byte) 0); + writer.putInt(0x64); + writer.put((byte) 0); + writer.put((byte) 0); + writer.put((byte) 0); + writer.putInt(0); + writer.putInt(0x10); + writer.putInt(0x10); + writer.putInt(0x10); + writer.putInt(0x0); + writer.putInt(0x0); + writer.putInt(0); + writer.putInt(0); + } + writer.putInt(0); // Male/Female + writer.putInt(this.charterType); // Charter Type + writer.putFloat(this.mapY1); + writer.putFloat(this.mapX1); + writer.putFloat(this.mapY2); + writer.putFloat(this.mapX2); + writer.putInt(this.stretchX); + writer.putInt(this.stretchY); + writer.putInt(this.locX); + writer.putInt(this.locY); + } + + public void updateDatabase() { + DbManager.RealmQueries.REALM_UPDATE(this); + } + + /** + * @return the charterType + */ + public int getCharterType() { + return charterType; + } + + /** + * @param charterType the charterType to set + */ + public void setCharterType(int charterType) { + this.charterType = charterType; + } + + public void abandonRealm() { + + // Push event to warehouse + + RealmRecord realmRecord = RealmRecord.borrow(this, Enum.RecordEventType.LOST); + DataWarehouse.pushToWarehouse(realmRecord); + + // No longer own a realm + this.getRulingCity().getGuild().setRealmsOwned(this.getRulingCity().getGuild().getRealmsOwned() - 1); + if (!this.getRulingCity().getGuild().getNation().equals(this.getRulingCity().getGuild())) + this.getRulingCity().getGuild().getNation().setRealmsOwned(this.getRulingCity().getGuild().getNation().getRealmsOwned() - 1); + + // Configure realm + this.charterType = 0; + this.rulingCityUUID = 0; + this.ruledSince = null; + + this.updateDatabase(); + } + + public void claimRealmForCity(City city, int charterType) { + + // Configure realm + this.charterType = charterType; + this.rulingCityUUID = city.getObjectUUID(); + this.ruledSince = LocalDateTime.now(); + this.configure(); + this.updateDatabase(); + + // Push event to warehouse + + RealmRecord realmRecord = RealmRecord.borrow(this, Enum.RecordEventType.CAPTURE); + DataWarehouse.pushToWarehouse(realmRecord); + + } + + public String getHash() { + return hash; + } + + public void setHash() { + + this.hash = DataWarehouse.hasher.encrypt(this.realmID); + + // Write hash to player character table + + DataWarehouse.writeHash(Enum.DataRecordType.REALM, this.realmID); + } + + /* *** Keeping around in case needed for server wipe or some other emergency + + public static void backfillRealms() { + + // Backfill realm records + + for (Realm realm : _realms.values()) { + + realm.setHash(); + + if ( (realm.isRuled() == true) && + (DataWarehouse.recordExists(Enum.DataRecordType.REALM, realm.getRealmID()) == false)) { + RealmRecord realmRecord = RealmRecord.borrow(realm, Enum.RecordEventType.CAPTURE); + DataWarehouse.pushToWarehouse(realmRecord); + } + + } + } + */ +} diff --git a/src/engine/objects/Regions.java b/src/engine/objects/Regions.java new file mode 100644 index 00000000..0b51baca --- /dev/null +++ b/src/engine/objects/Regions.java @@ -0,0 +1,376 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.InterestManagement.WorldGrid; +import engine.gameManager.BuildingManager; +import engine.math.Bounds; +import engine.math.FastMath; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.server.MBServerStatics; + +import java.util.ArrayList; +import java.util.HashMap; + + +public class Regions { + + public static HashMap FurnitureRegionMap = new HashMap<>(); + public final boolean stairs; + public final boolean exit; + public int room; + public boolean outside; + public int level; + public Vector3fImmutable lowLerp; + public Vector3fImmutable highLerp; + public Vector3f center; + public float regionDistanceSquared; + + public ArrayList regionPoints; + + public int parentBuildingID; + + + public Regions(ArrayList regionPoints, int level, int room, boolean outside, boolean exit, boolean stairs, Vector3f center, int parentBuildingID) { + super(); + + this.level = level; + this.room = room; + + this.outside = (outside); + this.regionPoints = regionPoints; + this.exit = exit; + this.stairs = stairs; + this.center = center; + this.parentBuildingID = parentBuildingID; + //order regionpoints clockwise starting from top left, and ending bottom left. + + ArrayList top = new ArrayList<>(); + ArrayList bottom = new ArrayList<>(); + + for (Vector3f point : this.regionPoints) { + if (point.y > center.y) + top.add(point); + else if (point.y < center.y) + bottom.add(point); + } + + + if (top.size() == 2 && bottom.size() == 2) { + Vector3f topLeft = Vector3f.min(top.get(0), top.get(1)); + Vector3f topRight = Vector3f.max(top.get(0), top.get(1)); + + Vector3f topCenter = topLeft.lerp(topRight, .5f); + + Vector3f bottomLeft = Vector3f.min(bottom.get(0), bottom.get(1)); + Vector3f bottomRight = Vector3f.max(bottom.get(0), bottom.get(1)); + + Vector3f bottomCenter = bottomLeft.lerp(bottomRight, .5f); + + this.lowLerp = new Vector3fImmutable(bottomCenter); + this.highLerp = new Vector3fImmutable(topCenter); + + } else if (top.size() == 2 && bottom.size() == 1) { + Vector3f topLeft = Vector3f.min(top.get(0), top.get(1)); + Vector3f topRight = Vector3f.max(top.get(0), top.get(1)); + + Vector3f topCenter = topLeft.lerp(topRight, .5f); + + Vector3f topCopy = topRight.subtract2D(topLeft); + topCopy.normalize(); + + float topMagnitude = topRight.subtract2D(topLeft).length(); + + topCopy.multLocal(topMagnitude); + + Vector3f bottomLeft = null; + Vector3f bottomRight = null; + if (bottom.get(0).distance2D(topLeft) <= bottom.get(0).distance2D(topRight)) + bottomLeft = bottom.get(0); + else if (bottom.get(0).distance2D(topRight) <= bottom.get(0).distance2D(topLeft)) + bottomRight = bottom.get(0); + //find bottom right point + + if (bottomLeft != null) { + bottomRight = bottomLeft.add(topCopy); + } else if (bottomRight != null) { + bottomLeft = bottomRight.subtract(topCopy); + } + + + Vector3f bottomCenter = bottomLeft.lerp(bottomRight, .5f); + + this.lowLerp = new Vector3fImmutable(bottomCenter); + this.highLerp = new Vector3fImmutable(topCenter); + + } else if (bottom.size() == 2 && top.size() == 1) { + Vector3f topLeft = Vector3f.min(bottom.get(0), bottom.get(1)); + Vector3f topRight = Vector3f.max(bottom.get(0), bottom.get(1)); + + Vector3f topCenter = topLeft.lerp(topRight, .5f); + + Vector3f topCopy = topRight.subtract2D(topLeft); + topCopy.normalize(); + + float topMagnitude = topRight.subtract2D(topLeft).length(); + + topCopy.multLocal(topMagnitude); + + Vector3f bottomLeft = null; + Vector3f bottomRight = null; + if (top.get(0).distance2D(topLeft) < top.get(0).distance2D(topRight)) + bottomLeft = bottom.get(0); + else if (top.get(0).distance2D(topRight) < top.get(0).distance2D(topLeft)) + bottomRight = bottom.get(0); + //find bottom right point + + if (bottomLeft != null) { + bottomRight = bottomLeft.add(topCopy); + } else if (bottomRight != null) { + bottomLeft = bottomRight.subtract(topCopy); + } + + + Vector3f bottomCenter = bottomLeft.lerp(bottomRight, .5f); + + this.lowLerp = new Vector3fImmutable(bottomCenter); + this.highLerp = new Vector3fImmutable(topCenter); + } + + if (this.lowLerp == null) + this.lowLerp = new Vector3fImmutable(this.regionPoints.get(0)); + + if (this.highLerp == null) { + this.highLerp = new Vector3fImmutable(this.regionPoints.get(2)); + } + + this.regionDistanceSquared = this.lowLerp.distanceSquared2D(this.highLerp); + } + + public static boolean CanEnterRegion(AbstractWorldObject worldObject, Regions toEnter) { + + if (worldObject.region == null) + if (toEnter.level == 0 || toEnter.room == -1 || toEnter.exit) + return true; + else + return false; + + if (worldObject.region.equals(toEnter)) + return true; + + if (worldObject.region.level == toEnter.level) + return true; + + //next region is stairs, if they are on the same level as stairs or 1 up, world object can enter. + if (toEnter.stairs) + if (worldObject.region.level == toEnter.level || toEnter.level - 1 == worldObject.region.level) + return true; + if (worldObject.region.stairs) { + + boolean movingUp = false; + + boolean movingDown = false; + float yLerp = worldObject.region.lerpY(worldObject); + + if (yLerp == (worldObject.region.highLerp.y)) + movingUp = true; + else if (yLerp == (worldObject.region.lowLerp.y)) + movingDown = true; + //Stairs are always considered on the bottom floor. + + + if (movingUp) { + if (toEnter.level == worldObject.region.level + 1) + return true; + } else if (movingDown) + if (toEnter.level == worldObject.region.level) + return true; + + } + + return false; + } + + public static float GetMagnitudeOfRegionSlope(Regions region) { + Vector3fImmutable lengthVector = region.highLerp.subtract2D(region.lowLerp); + return lengthVector.magnitude(); + } + + public static float GetMagnitudeOfPlayerOnRegionSlope(Regions region, PlayerCharacter player) { + Vector3fImmutable characterVector = player.getLoc().subtract2D(region.lowLerp); + return characterVector.magnitude(); + } + + public static float SlopeLerpPercent(PlayerCharacter player, Regions region) { + + float lengthVectorMagnitude = Regions.GetMagnitudeOfRegionSlope(region); + float characterVectorMagnitude = Regions.GetMagnitudeOfPlayerOnRegionSlope(region, player); + float percentDistance = characterVectorMagnitude / lengthVectorMagnitude * 2; + return percentDistance; + } + + public static boolean CanEnterFromOutsideBuilding(Building building, Regions region) { + if (!region.outside) + return false; + if (region.lowLerp.y - building.getLoc().y > 1) + return false; + + return true; + } + + public static boolean CanEnterNextLevel(Regions fromRegion, Regions toRegion, AbstractWorldObject worldObject) { + + if (fromRegion == null) + return false; + + if (toRegion == null) + return false; + + // regions are the same, no need to go any further. + if (fromRegion.equals(toRegion)) + return true; + + //cant move up a level without stairs. + if (!fromRegion.stairs) + return false; + + boolean movingUp = false; + + Vector3fImmutable closestPoint = Vector3fImmutable.ClosestPointOnLine(fromRegion.lowLerp, fromRegion.highLerp, worldObject.getLoc()); + + //Closest point of a region higher than current region will always return highlerp. + if (closestPoint.equals(fromRegion.highLerp)) + movingUp = true; + //Stairs are always considered on the bottom floor. + + if (movingUp) { + if (toRegion.level != fromRegion.level + 1) + return false; + } else if (toRegion.level != fromRegion.level) + return false; + return true; + } + + public static boolean IsGroundLevel(Regions region, Building building) { + + if (region.lowLerp.y - building.getLoc().y > 1) + return false; + return true; + } + + public static Building GetBuildingForRegion(Regions region) { + return BuildingManager.getBuildingFromCache(region.parentBuildingID); + } + + public static Regions GetRegionForTeleport(Vector3fImmutable location) { + Regions region = null; + + + //Find building + for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(location, 128, MBServerStatics.MASK_BUILDING)) { + Building building = (Building) awo; + if (!Bounds.collide(location, building.getBounds())) + continue; + if(building != null) { + region = BuildingManager.GetRegion(building, location.x, location.y, location.z); + } + //find regions that intersect x and z, check if object can enter. + //for (Regions toEnter : building.getBounds().getRegions()) { + // if (toEnter.isPointInPolygon(location)) { + + // if (region == null) + // region = toEnter; + // else // we're using a low level to high level tree structure, database not always in order low to high. + //check for highest level index. + // if (region != null && toEnter.highLerp.y > region.highLerp.y) + // region = toEnter; + + + // } + // } + } + return region; + } + + public int getRoom() { + return room; + } + + public boolean isOutside() { + return outside; + } + + public int getLevel() { + return level; + } + + public boolean collides(Vector3fImmutable collisionPoint) { + + //test if inside triangle // Regions either have 3 or 4 points + if (this.regionPoints.size() == 3) { + float regionArea = FastMath.area(regionPoints.get(0).x, regionPoints.get(0).z, regionPoints.get(1).x, regionPoints.get(1).z, regionPoints.get(2).x, regionPoints.get(2).z); + float collisionArea1 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(0).x, regionPoints.get(0).z, regionPoints.get(1).x, regionPoints.get(1).z); + float collisionArea2 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(1).x, regionPoints.get(1).z, regionPoints.get(2).x, regionPoints.get(2).z); + float collisionArea3 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(0).x, regionPoints.get(0).z, regionPoints.get(2).x, regionPoints.get(2).z); + + if ((collisionArea1 + collisionArea2 + collisionArea3) == regionArea) + return true; + + } else { + + int i; + int j; + for (i = 0, j = this.regionPoints.size() - 1; i < this.regionPoints.size(); j = i++) { + if ((regionPoints.get(i).z > collisionPoint.z) != (regionPoints.get(j).z > collisionPoint.z) && + (collisionPoint.x < (regionPoints.get(j).x - regionPoints.get(i).x) * (collisionPoint.z - regionPoints.get(i).z) / (regionPoints.get(j).z - regionPoints.get(i).z) + regionPoints.get(i).x)) { + return true; + } + } + + } + + return false; + } + + public boolean isPointInPolygon(Vector3fImmutable point) { + boolean inside = false; + for (int i = 0, j = regionPoints.size() - 1; i < regionPoints.size(); j = i++) { + if (((regionPoints.get(i).z > point.z) != (regionPoints.get(j).z > point.z)) && + (point.x < (regionPoints.get(j).x - regionPoints.get(i).x) * (point.z - regionPoints.get(i).z) / (regionPoints.get(j).z - regionPoints.get(i).z) + regionPoints.get(i).x)) + inside = !inside; + } + return inside; + } + + public float lerpY(AbstractWorldObject lerper) { + + Vector3fImmutable lengthVector = this.highLerp.subtract2D(this.lowLerp); + Vector3fImmutable characterVector = lerper.getLoc().subtract2D(this.lowLerp); + float lengthVectorMagnitude = lengthVector.magnitude(); + float characterVectorMagnitude = characterVector.magnitude(); + float percentDistance = characterVectorMagnitude / lengthVectorMagnitude; + float interpolatedY = this.lowLerp.interpolate(this.highLerp, percentDistance).y; + + if (interpolatedY > this.highLerp.y) + interpolatedY = this.highLerp.y; + else if (interpolatedY < this.lowLerp.y) + interpolatedY = this.lowLerp.y; + return interpolatedY; + } + + public boolean isStairs() { + return stairs; + } + + public boolean isExit() { + return exit; + } +} diff --git a/src/engine/objects/Resists.java b/src/engine/objects/Resists.java new file mode 100644 index 00000000..e72b1827 --- /dev/null +++ b/src/engine/objects/Resists.java @@ -0,0 +1,562 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.DamageType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.powers.EffectsBase; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + +public class Resists { + + private static ConcurrentHashMap mobResists = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private ConcurrentHashMap resists = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private ConcurrentHashMap immuneTo = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private DamageType protection; + private int protectionTrains = 0; + private boolean immuneToAll; + + /** + * Generic Constructor + */ + + public Resists(String type) { + switch (type) { + case "Building": + setBuildingResists(); + break; + case "Mine": + setMineResists(); + break; + default: + setGenericResists(); + break; + } + } + + public Resists(Resists r) { + for (DamageType dt : r.resists.keySet()) + this.resists.put(dt, r.resists.get(dt)); + for (DamageType dt : r.immuneTo.keySet()) + this.immuneTo.put(dt, r.immuneTo.get(dt)); + this.protection = r.protection; + this.protectionTrains = r.protectionTrains; + this.immuneToAll = r.immuneToAll; + } + + /** + * Generic Constructor for player + */ + public Resists(PlayerCharacter pc) { + setGenericResists(); + } + + public Resists(Mob mob) { + setGenericResists(); + } + + /** + * Called for mobBase when getting from the db fails + */ + public Resists(MobBase mobBase) { + setGenericResists(); + } + + /** + * Database Constructor + */ + public Resists(ResultSet rs) throws SQLException { + this.immuneToAll = false; + this.resists.put(DamageType.Slash, rs.getFloat("slash")); + this.resists.put(DamageType.Crush, rs.getFloat("crush")); + this.resists.put(DamageType.Pierce, rs.getFloat("pierce")); + this.resists.put(DamageType.Magic, rs.getFloat("magic")); + this.resists.put(DamageType.Bleed, rs.getFloat("bleed")); + this.resists.put(DamageType.Poison, rs.getFloat("poison")); + this.resists.put(DamageType.Mental, rs.getFloat("mental")); + this.resists.put(DamageType.Holy, rs.getFloat("holy")); + this.resists.put(DamageType.Unholy, rs.getFloat("unholy")); + this.resists.put(DamageType.Lightning, rs.getFloat("lightning")); + this.resists.put(DamageType.Fire, rs.getFloat("fire")); + this.resists.put(DamageType.Cold, rs.getFloat("cold")); + this.resists.put(DamageType.Healing, 0f); + } + + //Handle Fortitudes + private static float handleFortitude(AbstractCharacter target, DamageType type, float damage) { + if (target == null || !(target.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return damage; + PlayerBonuses bonus = target.getBonuses(); + + //see if there is a fortitude + float damageCap = bonus.getFloatPercentAll(ModType.DamageCap, SourceType.None); + if (damageCap == 0f || type == DamageType.Healing) + return damage; + + //is fortitude, Are we under the cap? + float maxHealth = target.getHealthMax(); + float capFire = maxHealth * (damageCap); + if (damage < capFire) + return damage; + + //let's see if valid damagetype to apply it + boolean exclusive; + HashSet forts = bonus.getList(ModType.IgnoreDamageCap); + if (forts == null) { + exclusive = true; + forts = bonus.getList(ModType.ExclusiveDamageCap); + } else + exclusive = false; + if (forts == null || !isValidDamageCapType(forts, type, exclusive)) + return damage; + + float adjustedDamage = bonus.getFloatPercentAll(ModType.AdjustAboveDmgCap, SourceType.None); + //Adjust damage down and return new amount + float aadc = 1 + adjustedDamage; + return capFire * aadc; + } + + //Test if Damagetype is valid for foritude + private static boolean isValidDamageCapType(HashSet forts, DamageType damageType, boolean exclusive) { + for (SourceType fort : forts) { + DamageType dt = DamageType.valueOf(fort.name()); + + if (dt == DamageType.None) + continue; + + if (dt == damageType) { + return exclusive; + } + } + return !exclusive; + } + + /** + * Calculate Current Resists for Player + */ + public static void calculateResists(AbstractCharacter ac) { + if (ac.getResists() != null) + ac.getResists().calculateResists(ac, true); + else + Logger.error("Unable to find resists for character " + ac.getObjectUUID()); + } + + private static float[] getArmorResists(Item armor, float[] phys) { + if (armor == null) + return phys; + ItemBase ab = armor.getItemBase(); + if (ab == null) + return phys; + phys[0] += ab.getSlashResist(); + phys[1] += ab.getCrushResist(); + phys[2] += ab.getPierceResist(); + return phys; + } + + /** + * Get mob resists from db if there, otherwise set defaults + */ + public static Resists getResists(int resistID) { + //check cache first + if (mobResists.containsKey(resistID)) + return new Resists(mobResists.get(resistID)); + + //get from database + Resists resists = DbManager.ResistQueries.GET_RESISTS_FOR_MOB(resistID); + if (resists != null) { + mobResists.put(resistID, resists); + return new Resists(resists); + } + + //failed, may want to debug this + return null; + } + + /** + * Create generic resists for buildings + */ + public final void setBuildingResists() { + this.immuneToAll = false; + this.resists.put(DamageType.Slash, 85f); + this.resists.put(DamageType.Crush, 85f); + this.resists.put(DamageType.Siege, 0f); + this.immuneTo.put(DamageType.Pierce, true); + this.immuneTo.put(DamageType.Magic, true); + this.immuneTo.put(DamageType.Bleed, true); + this.immuneTo.put(DamageType.Poison, true); + this.immuneTo.put(DamageType.Mental, true); + this.immuneTo.put(DamageType.Holy, true); + this.immuneTo.put(DamageType.Unholy, true); + this.immuneTo.put(DamageType.Lightning, true); + this.immuneTo.put(DamageType.Fire, true); + this.immuneTo.put(DamageType.Cold, true); + + } + + /** + * Create generic resists for mines + */ + public final void setMineResists() { + this.immuneToAll = false; + this.immuneTo.put(DamageType.Slash, true); + this.immuneTo.put(DamageType.Crush, true); + this.immuneTo.put(DamageType.Pierce, true); + this.immuneTo.put(DamageType.Magic, true); + this.immuneTo.put(DamageType.Bleed, true); + this.immuneTo.put(DamageType.Poison, true); + this.immuneTo.put(DamageType.Mental, true); + this.immuneTo.put(DamageType.Holy, true); + this.immuneTo.put(DamageType.Unholy, true); + this.immuneTo.put(DamageType.Lightning, true); + this.immuneTo.put(DamageType.Fire, true); + this.immuneTo.put(DamageType.Cold, true); + this.resists.put(DamageType.Siege, 0f); + } + + /** + * Create generic resists + */ + public final void setGenericResists() { + this.immuneToAll = false; + this.resists.put(DamageType.Slash, 0f); + this.resists.put(DamageType.Crush, 0f); + this.resists.put(DamageType.Pierce, 0f); + this.resists.put(DamageType.Magic, 0f); + this.resists.put(DamageType.Bleed, 0f); + this.resists.put(DamageType.Poison, 0f); + this.resists.put(DamageType.Mental, 0f); + this.resists.put(DamageType.Holy, 0f); + this.resists.put(DamageType.Unholy, 0f); + this.resists.put(DamageType.Lightning, 0f); + this.resists.put(DamageType.Fire, 0f); + this.resists.put(DamageType.Cold, 0f); + this.resists.put(DamageType.Healing, 0f); + this.immuneTo.put(DamageType.Siege, true); + + } + + /** + * Get a resist + */ + public float getResist(DamageType type, int trains) { + //get resisted amount + Float amount = 0f; + if (this.resists.containsKey(type)) + amount = this.resists.get(type); + + //add protection + if (trains > 0 && protection != null && type.equals(this.protection)) { + float prot = 50 + this.protectionTrains - trains; + amount += (prot >= 0) ? prot : 0; + } + + if (amount == null) + return 0f; + if (amount > 75f) + return 75f; + return amount; + } + + /** + * get immuneTo + */ + public boolean immuneTo(DamageType type) { + if (this.immuneTo.containsKey(type)) + return this.immuneTo.get(type); + else + return false; + } + + /** + * get immuneToAll + */ + public boolean immuneToAll() { + return this.immuneToAll; + } + + public boolean immuneToPowers() { + return immuneTo(DamageType.Powers); + } + + public boolean immuneToAttacks() { + return immuneTo(DamageType.Attack); + } + + public boolean immuneToSpires() { + return immuneTo(DamageType.Spires); + } + + /** + * gets immuneTo(type) and immuneToAll + */ + public boolean isImmune(DamageType type) { + if (this.immuneToAll) + return true; + return this.immuneTo(type); + } + + /** + * Set a resist + */ + public void setResist(DamageType type, float value) { + this.resists.put(type, value); + } + + /** + * add to a resist + */ + public void incResist(DamageType type, float value) { + Float amount = this.resists.get(type); + if (amount == null) + this.resists.put(type, value); + else + this.resists.put(type, amount + value); + } + + /** + * subtract from a resist + */ + public void decResist(DamageType type, float value) { + Float amount = this.resists.get(type); + if (amount == null) + this.resists.put(type, (0 - value)); + else + this.resists.put(type, amount - value); + } + + /** + * set immunities from mobbase + */ + public void setImmuneTo(int immune) { + setImmuneTo(DamageType.Stun, ((immune & 1) != 0)); + setImmuneTo(DamageType.PowerBlock, ((immune & 2) != 0)); + setImmuneTo(DamageType.Drain, ((immune & 4) != 0)); + setImmuneTo(DamageType.Snare, ((immune & 8) != 0)); + setImmuneTo(DamageType.Siege, ((immune & 16) != 0)); + setImmuneTo(DamageType.Slash, ((immune & 32) != 0)); + setImmuneTo(DamageType.Crush, ((immune & 64) != 0)); + setImmuneTo(DamageType.Pierce, ((immune & 128) != 0)); + setImmuneTo(DamageType.Magic, ((immune & 256) != 0)); + setImmuneTo(DamageType.Bleed, ((immune & 512) != 0)); + setImmuneTo(DamageType.Poison, ((immune & 1024) != 0)); + setImmuneTo(DamageType.Mental, ((immune & 2048) != 0)); + setImmuneTo(DamageType.Holy, ((immune & 4096) != 0)); + setImmuneTo(DamageType.Unholy, ((immune & 8192) != 0)); + setImmuneTo(DamageType.Lightning, ((immune & 16384) != 0)); + setImmuneTo(DamageType.Fire, ((immune & 32768) != 0)); + setImmuneTo(DamageType.Cold, ((immune & 65536) != 0)); + setImmuneTo(DamageType.Steel, ((immune & 131072) != 0)); + } + + /** + * set/unset immuneTo + */ + public void setImmuneTo(DamageType type, boolean value) { + this.immuneTo.put(type, value); + } + + /** + * set immuneToAll + */ + public void setImmuneToAll(boolean value) { + this.immuneToAll = value; + } + + /** + * set resists from mobbase + */ + public void setMobResists(int resistID) { + //TODO add this in later + //calls `static_npc_mob_resists` table WHERE `ID`='resistID' + } + + /** + * get Damage after resist + * Expects heals as negative damage and damage as positive damage for fortitudes. + */ + public float getResistedDamage(AbstractCharacter source, AbstractCharacter target, DamageType type, float damage, int trains) { + //handle fortitudes + damage = handleFortitude(target, type, damage); + //calculate armor piercing + float ap = source.getBonuses().getFloatPercentAll(ModType.ArmorPiercing, SourceType.None); + float damageAfterResists = damage * (1 - (this.getResist(type, trains) * 0.01f) + ap); + //check to see if any damage absorbers should cancel + if (target != null) { + //debug damage shields if any found + if (source.getDebug(2) && source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + Effect da = target.getDamageAbsorber(); + if (da != null && da.getEffectsBase() != null) { + EffectsBase eb = da.getEffectsBase(); + String text = "Damage: " + damage + '\n'; + text += "Damage after resists: " + damageAfterResists + '\n'; + text += "Attack damage type: " + type.name() + '\n'; + text += "Fortitude damage types; " + eb.getDamageTypes() + '\n'; + text += "Fortitude damage before attack: " + da.getDamageAmount() + '\n'; + text += "Fortitude total health: " + eb.getDamageAmount(da.getTrains()) + '\n'; + text += "Fortitude trains: " + da.getTrains(); + ChatManager.chatSystemInfo((PlayerCharacter) source, text); + } + } + target.cancelOnTakeDamage(type, (damageAfterResists)); + } + return damageAfterResists; + } + + public void calculateResists(AbstractCharacter ac, boolean val) { + this.immuneTo.clear(); + + // get resists for runes + PlayerBonuses rb = ac.getBonuses(); + float slash = 0f, crush = 0f, pierce = 0f, magic = 0f, bleed = 0f, mental = 0f, holy = 0f, unholy = 0f, poison = 0f, lightning = 0f, fire = 0f, cold = 0f, healing = 0f; + + if (rb != null) { + // Handle immunities + if (rb.getBool(ModType.ImmuneTo, SourceType.Stun)) + this.immuneTo.put(DamageType.Stun, true); + if (rb.getBool(ModType.ImmuneTo, SourceType.Blind)) + this.immuneTo.put(DamageType.Blind, true); + if (rb.getBool(ModType.ImmuneToAttack, SourceType.None)) + this.immuneTo.put(DamageType.Attack, true); + if (rb.getBool(ModType.ImmuneToPowers, SourceType.None)) + this.immuneTo.put(DamageType.Powers, true); + if (rb.getBool(ModType.ImmuneTo, SourceType.Powerblock)) + this.immuneTo.put(DamageType.Powerblock, true); + if (rb.getBool(ModType.ImmuneTo, SourceType.DeBuff)) + this.immuneTo.put(DamageType.DeBuff, true); + if (rb.getBool(ModType.ImmuneTo, SourceType.Fear)) + this.immuneTo.put(DamageType.Fear, true); + if (rb.getBool(ModType.ImmuneTo, SourceType.Charm)) + this.immuneTo.put(DamageType.Charm, true); + if (rb.getBool(ModType.ImmuneTo, SourceType.Root)) + this.immuneTo.put(DamageType.Root, true); + if (rb.getBool(ModType.ImmuneTo, SourceType.Snare)) + this.immuneTo.put(DamageType.Snare, true); + + // Handle resists + slash += rb.getFloat(ModType.Resistance, SourceType.Slash); + crush += rb.getFloat(ModType.Resistance, SourceType.Crush); + pierce += rb.getFloat(ModType.Resistance, SourceType.Pierce); + magic += rb.getFloat(ModType.Resistance, SourceType.Magic); + bleed += rb.getFloat(ModType.Resistance, SourceType.Bleed); + poison += rb.getFloat(ModType.Resistance, SourceType.Poison); + mental += rb.getFloat(ModType.Resistance, SourceType.Mental); + holy += rb.getFloat(ModType.Resistance, SourceType.Holy); + unholy += rb.getFloat(ModType.Resistance, SourceType.Unholy); + lightning += rb.getFloat(ModType.Resistance, SourceType.Lightning); + fire += rb.getFloat(ModType.Resistance, SourceType.Fire); + cold += rb.getFloat(ModType.Resistance, SourceType.Cold); + healing += rb.getFloat(ModType.Resistance, SourceType.Healing); // DamageType.Healing.name()); + + //HHO + +// String protectionString = rb.getString("protection"); +// +// if (protectionString.isEmpty()) +// this.protection = null; +// else try { +// this.protection = DamageType.valueOf(rb.getString("protection")); +// } catch (IllegalArgumentException e) { +// Logger.error( "No enum for: " + protectionString); +// this.protection = null; +// } +// this.protectionTrains = rb.getFloat("protection"); + } + + // get resists from equipment + if (ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + if (ac.getCharItemManager() != null && ac.getCharItemManager().getEquipped() != null) { + float[] phys = {0f, 0f, 0f}; + ConcurrentHashMap equip = ac.getCharItemManager().getEquipped(); + + // get base physical resists + phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_HELMET), phys); + phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_CHEST), phys); + phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_ARMS), phys); + phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_GLOVES), phys); + phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_LEGGINGS), phys); + phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_FEET), phys); + slash += phys[0]; + crush += phys[1]; + pierce += phys[2]; + + } + } + + this.resists.put(DamageType.Slash, slash); + this.resists.put(DamageType.Crush, crush); + this.resists.put(DamageType.Pierce, pierce); + this.resists.put(DamageType.Magic, magic); + this.resists.put(DamageType.Bleed, bleed); + this.resists.put(DamageType.Poison, poison); + this.resists.put(DamageType.Mental, mental); + this.resists.put(DamageType.Holy, holy); + this.resists.put(DamageType.Unholy, unholy); + this.resists.put(DamageType.Lightning, lightning); + this.resists.put(DamageType.Fire, fire); + this.resists.put(DamageType.Cold, cold); + this.resists.put(DamageType.Healing, healing); + + this.immuneTo.put(DamageType.Siege, true); + + // debug printing of resists + // printResists(pc); + } + + public void printResistsToClient(PlayerCharacter pc) { + for (DamageType dt : resists.keySet()) + ChatManager.chatSystemInfo(pc, " resist." + dt.name() + ": " + resists.get(dt)); + for (DamageType dt : immuneTo.keySet()) + ChatManager.chatSystemInfo(pc, " immuneTo." + dt.name() + ": " + immuneTo.get(dt)); + ChatManager.chatSystemInfo(pc, " immuneToAll: " + this.immuneToAll); + if (protection != null) + ChatManager.chatSystemInfo(pc, " Protection: " + protection.name() + ", Trains: " + protectionTrains); + else + ChatManager.chatSystemInfo(pc, " Protection: None"); + } + + public String getResists(PlayerCharacter pc) { + String out = pc.getName(); + + out += "Resists: "; + Iterator it = this.resists.keySet().iterator(); + while (it.hasNext()) { + DamageType damType = it.next(); + String dtName = damType.name(); + out += dtName + '=' + this.resists.get(dtName) + ", "; + } + + out += "ImmuneTo: "; + it = this.immuneTo.keySet().iterator(); + while (it.hasNext()) { + DamageType damType = it.next(); + + String dtName = damType.name(); + out += dtName + '=' + this.resists.get(dtName) + ", "; + } + + if (protection != null) + out += "Protection: " + protection.name() + ", Trains: " + protectionTrains; + else + out += "Protection: none"; + + return out; + } +} diff --git a/src/engine/objects/Resource.java b/src/engine/objects/Resource.java new file mode 100644 index 00000000..995cecf5 --- /dev/null +++ b/src/engine/objects/Resource.java @@ -0,0 +1,70 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.server.MBServerStatics; + +import java.util.concurrent.ConcurrentHashMap; + +public enum Resource { + + ADAMANT("DefaultAdamant", 1557001525, 10, 1580003), + AGATE("DefaultAgate", -1096157543, 20, 1580009), + ANTIMONY("DefaultAntimony", 1256147265, 10, 1580014), + AZOTH("DefaultAzoth", -1205326951, 20, 1580012), + BLOODSTONE("DefaultBloodstone", -1912381716, 5, 1580020), + BRONZEWOOD("DefaultBronzewood", -519681813, 30, 1580006), + COAL("DefaultCoal", -1672872311, 30, 1580008), + DIAMOND("DefaultDiamond", 1540225085, 20, 1580010), + GALVOR("DefaultGalvor", -1683992404, 5, 1580017), + IRON("DefaultIron", -1673518119, 20, 1580002), + LUMBER("DefaultLumber", -1628412684, 100, 1580004), + MANDRAKE("DefaultMandrake", -1519910613, 10, 1580007), + MITHRIL("DefaultMithril", 626743397, 5, 1580021), + OAK("DefaultOak", -1653034775, 30, 1580005), + OBSIDIAN("DefaultObsidian", 778019055, 5, 1580019), + ONYX("DefaultOnyx", -1675952151, 10, 1580011), + ORICHALK("DefaultOrichalk", -1468730955, 30, 1580013), + QUICKSILVER("DefaultQuicksilver", -2081208434, 10, 1580016), + STONE("DefaultStone", -1094703863, 100, 1580000), + SULFUR("DefaultSulfur", -1763687412, 10, 1580015), + TRUESTEEL("DefaultTruesteel", -169012482, 20, 1580001), + WORMWOOD("DefaultWormwood", 1204785075, 5, 1580018), + GOLD("DefaultGold", -1670881623, 50000, 7); + + public static ConcurrentHashMap resourceByHash; + + //load lookups via hashes + static { + resourceByHash = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + for (Resource r : Resource.values()) + resourceByHash.put(r.hash, r); + } + + public final String name; + public final int hash; + public final int baseProduction; + public final int UUID; + + Resource(String name, int hash, int baseProduction, int uuid) { + this.name = name; + this.hash = hash; + this.baseProduction = baseProduction; + this.UUID = uuid; + } + + public static Resource GetResourceByHash(int hash) { + for (Resource resource : Resource.values()) { + if (hash == resource.hash) + return resource; + } + return Resource.MITHRIL; + } +} diff --git a/src/engine/objects/RuneBase.java b/src/engine/objects/RuneBase.java new file mode 100644 index 00000000..39950a6b --- /dev/null +++ b/src/engine/objects/RuneBase.java @@ -0,0 +1,215 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + + +public class RuneBase extends AbstractGameObject { + + public static HashMap> AllowedBaseClassRunesMap = new HashMap<>(); + public static HashMap> AllowedRaceRunesMap = new HashMap<>(); + private final String name; + private final String description; + private final int type; + private final byte subtype; + private final ConcurrentHashMap race = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final ConcurrentHashMap baseClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final ConcurrentHashMap promotionClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final ConcurrentHashMap discipline = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private final ArrayList overwrite = new ArrayList<>(); + private int levelRequired = 1; + private ArrayList effectsList = new ArrayList<>(); + + /** + * No Table ID Constructor + */ + public RuneBase(String name, String description, int type, byte subtype, ArrayList attrs) { + super(); + + this.name = name; + this.description = description; + this.type = type; + this.subtype = subtype; + + } + + /** + * Normal Constructor + */ + public RuneBase(String name, String description, int type, byte subtype, ArrayList attrs, int newUUID) { + super(newUUID); + + this.name = name; + this.description = description; + this.type = type; + this.subtype = subtype; + + } + + /** + * ResultSet Constructor + */ + public RuneBase(ResultSet rs) throws SQLException { + super(rs); + + this.name = rs.getString("name"); + this.description = rs.getString("description"); + this.type = rs.getInt("type"); + this.subtype = rs.getByte("subtype"); + + DbManager.RuneBaseQueries.GET_RUNE_REQS(this); + this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); + } + + public static RuneBase getRuneBase(int tableId) { + + if (tableId == 0) + return null; + + RuneBase rb = (RuneBase) DbManager.getFromCache(Enum.GameObjectType.RuneBase, tableId); + + if (rb != null) + return rb; + + return DbManager.RuneBaseQueries.GET_RUNEBASE(tableId); + } + + public static void serializeForClientMsg(RuneBase runeBase, ByteBufferWriter writer) { + writer.putInt(runeBase.type); + writer.putInt(0); // Pad + writer.putInt(runeBase.getObjectUUID()); + writer.putInt(runeBase.getObjectType().ordinal()); + writer.putInt(runeBase.getObjectUUID()); + + } + + public static void LoadAllRuneBases() { + + DbManager.RuneBaseQueries.LOAD_ALL_RUNEBASES(); + RuneBase.AllowedBaseClassRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS(); + RuneBase.AllowedRaceRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_RACE(); + } + + @Override + public boolean equals(Object obj) { + + if (!super.equals(obj)) { + return false; + } + + if (obj instanceof RuneBase) { + RuneBase rbObj = (RuneBase) obj; + if (!this.name.equals(rbObj.name)) { + return false; + } + + if (!this.description.equals(rbObj.description)) { + return false; + } + + if (this.type != rbObj.type) { + return false; + } + + if (this.subtype != rbObj.subtype) { + return false; + } + + + return true; + } + + return false; + } + + /* + * Getters + */ + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public int getType() { + return type; + } + + /** + * @return the subtype + */ + public byte getSubtype() { + return subtype; + } + + /** + * @return the attrs + */ + public ArrayList getAttrs() { + return RuneBaseAttribute.runeBaseAttributeMap.get(this.getObjectUUID()); + } + + public ConcurrentHashMap getRace() { + return this.race; + } + + public ConcurrentHashMap getBaseClass() { + return this.baseClass; + } + + public ConcurrentHashMap getPromotionClass() { + return this.promotionClass; + } + + public ConcurrentHashMap getDiscipline() { + return this.discipline; + } + + public ArrayList getOverwrite() { + return this.overwrite; + } + + /* + * Serializing + */ + + public int getLevelRequired() { + return this.levelRequired; + } + + public void setLevelRequired(int levelRequired) { + this.levelRequired = levelRequired; + } + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + } + + /** + * @return the effectsList + */ + public ArrayList getEffectsList() { + return effectsList; + } + +} diff --git a/src/engine/objects/RuneBaseAttribute.java b/src/engine/objects/RuneBaseAttribute.java new file mode 100644 index 00000000..82e93b22 --- /dev/null +++ b/src/engine/objects/RuneBaseAttribute.java @@ -0,0 +1,103 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.GameObjectType; +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + + +public class RuneBaseAttribute extends AbstractGameObject { + + public static HashMap> runeBaseAttributeMap = new HashMap<>(); + private short attributeID; + private short modValue; + private int runeBaseID; + + + /** + * No Table ID Constructor + */ + public RuneBaseAttribute(short attributeID, short modValue) { + super(); + + this.attributeID = attributeID; + this.modValue = modValue; + } + + /** + * Normal + */ + public RuneBaseAttribute(short attributeID, short modValue, int newUUID) { + super(newUUID); + + this.attributeID = attributeID; + this.modValue = modValue; + } + + /** + * ResultSet Constructor + */ + public RuneBaseAttribute(ResultSet rs) throws SQLException { + super(rs); + + this.attributeID = rs.getShort("attributeID"); + this.modValue = rs.getShort("modValue"); + this.runeBaseID = rs.getInt("RuneBaseID"); + } + + public static void LoadAllAttributes() { + DbManager.RuneBaseAttributeQueries.GET_ATTRIBUTES_FOR_RUNEBASE(); + + + //cache attributeLists for rune. + for (AbstractGameObject ago : DbManager.getList(GameObjectType.RuneBaseAttribute)) { + + RuneBaseAttribute runeBaseAttribute = (RuneBaseAttribute) ago; + + int runeBaseID = ((RuneBaseAttribute) runeBaseAttribute).runeBaseID; + if (runeBaseAttributeMap.get(runeBaseID) == null) { + ArrayList attributeList = new ArrayList<>(); + attributeList.add(runeBaseAttribute); + runeBaseAttributeMap.put(runeBaseID, attributeList); + } else { + ArrayList attributeList = runeBaseAttributeMap.get(runeBaseID); + attributeList.add(runeBaseAttribute); + runeBaseAttributeMap.put(runeBaseID, attributeList); + } + + } + + } + + /* + * Getters + */ + public short getAttributeID() { + return attributeID; + } + + public short getModValue() { + return modValue; + } + + /* + * Utils + */ + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + } +} diff --git a/src/engine/objects/RuneBaseEffect.java b/src/engine/objects/RuneBaseEffect.java new file mode 100644 index 00000000..3a285e44 --- /dev/null +++ b/src/engine/objects/RuneBaseEffect.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + + +public class RuneBaseEffect extends AbstractGameObject { + + public static HashMap> RuneIDBaseEffectMap = new HashMap<>(); + private byte type; + private String name; + private short amount; + private int runeBaseID; + + /** + * ResultSet Constructor + */ + public RuneBaseEffect(ResultSet rs) throws SQLException { + super(rs); + this.type = rs.getByte("type"); + this.name = rs.getString("name"); + this.amount = rs.getShort("amount"); + this.runeBaseID = rs.getInt("runeID"); + } + + /* + * Getters + */ + + public static void LoadRuneBaseEffects() { + //cache runebase effects. + DbManager.RuneBaseEffectQueries.GET_ALL_RUNEBASE_EFFECTS(); + //store runebase effects in new hashmap. + RuneBaseEffect.RuneIDBaseEffectMap = DbManager.RuneBaseEffectQueries.LOAD_BASEEFFECTS_FOR_RUNEBASE(); + } + + public int getType() { + return this.type; + } + + public String getName() { + return this.name; + } + + public short getAmount() { + return this.amount; + } + + @Override + public void updateDatabase() { + + } + + public int getRuneBaseID() { + return runeBaseID; + } + +} \ No newline at end of file diff --git a/src/engine/objects/Runegate.java b/src/engine/objects/Runegate.java new file mode 100644 index 00000000..762265a5 --- /dev/null +++ b/src/engine/objects/Runegate.java @@ -0,0 +1,117 @@ +package engine.objects; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.net.ByteBufferWriter; + +import java.util.ArrayList; +import java.util.HashMap; + +/* Runegates are tied to particular buildings at + * bootstrap derived from the Portal creation. + * This is only to enable the toggling of effect + * bits when traveler is used. + */ + +public class Runegate { + + // Runegate class Instance variables + public static HashMap _runegates = new HashMap<>(); + + public Portal[] _portals; + public Building gateBuilding; + + private Runegate(Building gateBuilding) { + + this._portals = new Portal[8]; + this.gateBuilding = gateBuilding; + + // Load portals for this runegate portals from the database + + configurePortals(); + + // Chaos, Khar and Oblivion are on by default + + _portals[Enum.PortalType.CHAOS.ordinal()].activate(false); + _portals[Enum.PortalType.OBLIV.ordinal()].activate(false); + _portals[Enum.PortalType.MERCHANT.ordinal()].activate(false); + + } + + public static void loadAllRunegates() { + + ArrayList gateList; + + gateList = DbManager.RunegateQueries.GET_RUNEGATE_LIST(); + + for (int gateID : gateList) { + + Building gateBuilding = (Building) DbManager.getObject(Enum.GameObjectType.Building, gateID); + + Runegate runegate = new Runegate(gateBuilding); + _runegates.put(gateID, runegate); + } + + } + + public static ArrayList GetAllOpenGateIDStrings() { + ArrayList openGateIDStrings = new ArrayList<>(); + + openGateIDStrings.add("TRA-003"); + openGateIDStrings.add("TRA-004"); + openGateIDStrings.add("TRA-005"); + openGateIDStrings.add("TRA-006"); + openGateIDStrings.add("TRA-007"); + openGateIDStrings.add("TRA-008"); + openGateIDStrings.add("TRA-009"); + openGateIDStrings.add("TRA-010"); + return openGateIDStrings; + } + + public void activatePortal(Enum.PortalType portalType) { + + this._portals[portalType.ordinal()].activate(true); + + } + + public void deactivatePortal(Enum.PortalType portalType) { + + this._portals[portalType.ordinal()].deactivate(); + + } + + public Portal[] getPortals() { + + return this._portals; + + } + + public void collidePortals() { + + for (Portal portal : this.getPortals()) { + + if (portal.isActive()) + portal.collide(); + } + } + + public void configurePortals() { + + ArrayList portalList = DbManager.RunegateQueries.GET_PORTAL_LIST(this.gateBuilding.getObjectUUID()); + + for (Portal portal : portalList) { + this._portals[portal.portalType.ordinal()] = portal; + } + } + + public void _serializeForEnterWorld(ByteBufferWriter writer) { + + writer.putInt(gateBuilding.getObjectType().ordinal()); + writer.putInt(gateBuilding.getObjectUUID()); + writer.putString(gateBuilding.getParentZone().getName()); + writer.putFloat(gateBuilding.getLoc().getLat()); + writer.putFloat(gateBuilding.getLoc().getAlt()); + writer.putFloat(gateBuilding.getLoc().getLong()); + } + +} diff --git a/src/engine/objects/Shrine.java b/src/engine/objects/Shrine.java new file mode 100644 index 00000000..928be579 --- /dev/null +++ b/src/engine/objects/Shrine.java @@ -0,0 +1,353 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.ShrineType; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class Shrine extends AbstractWorldObject implements Comparable { + + public static ConcurrentHashMap shrinesByBuildingUUID = new ConcurrentHashMap<>(); + private final ShrineType shrineType; + private final int buildingID; + private Integer favors; + + /** + * ResultSet Constructor + */ + public Shrine(ResultSet rs) throws SQLException { + super(rs); + this.shrineType = ShrineType.valueOf(rs.getString("shrine_type")); + this.favors = rs.getInt("shrine_favors"); + this.buildingID = rs.getInt("parent"); + shrinesByBuildingUUID.put(this.buildingID, this); + } + + // Decays this shrine's favor by 10% + + public static boolean canTakeFavor(PlayerCharacter grantee, Shrine shrine) { + + if (shrine.shrineType.isRace()) + switch (grantee.getRaceID()) { + case 2000: + case 2001: + if (shrine.shrineType == ShrineType.Aelfborn) + return true; + break; + case 2002: + case 2003: + if (shrine.shrineType == ShrineType.Aracoix) + return true; + break; + case 2004: + case 2005: + if (shrine.shrineType == ShrineType.Centaur) + return true; + break; + case 2006: + if (shrine.shrineType == ShrineType.Dwarf) + return true; + break; + case 2008: + case 2009: + if (shrine.shrineType == ShrineType.Elf) + return true; + break; + case 2010: + case 2027: + if (shrine.shrineType == ShrineType.HalfGiant) + return true; + break; + case 2011: + case 2012: + if (shrine.shrineType == ShrineType.Human) + return true; + break; + case 2013: + case 2014: + if (shrine.shrineType == ShrineType.Irekei) + return true; + break; + case 2015: + case 2016: + if (shrine.shrineType == ShrineType.Shade) + return true; + break; + case 2017: + if (shrine.shrineType == ShrineType.Minotaur) + return true; + break; + + case 2025: + case 2026: + if (shrine.shrineType == ShrineType.Nephilim) + return true; + break; + case 2028: + case 2029: + if (shrine.shrineType == ShrineType.Vampire) + return true; + break; + + } + else + switch (grantee.getPromotionClassID()) { + case 2504: + if (shrine.shrineType == ShrineType.Assassin) + return true; + break; + case 2505: + if (shrine.shrineType == ShrineType.Barbarian) + return true; + break; + case 2506: + if (shrine.shrineType == ShrineType.Bard) + return true; + break; + case 2507: + if (shrine.shrineType == ShrineType.Channeler) + return true; + break; + case 2508: + if (shrine.shrineType == ShrineType.Confessor) + return true; + break; + case 2509: + if (shrine.shrineType == ShrineType.Crusader) + return true; + break; + case 2510: + if (shrine.shrineType == ShrineType.Druid) + return true; + break; + case 2511: + if (shrine.shrineType == ShrineType.Fury) + return true; + break; + case 2512: + if (shrine.shrineType == ShrineType.Huntress) + return true; + break; + case 2513: + if (shrine.shrineType == ShrineType.Prelate) + return true; + break; + case 2514: + if (shrine.shrineType == ShrineType.Ranger) + return true; + break; + case 2515: + if (shrine.shrineType == ShrineType.Scout) + return true; + break; + case 2516: + if (shrine.shrineType == ShrineType.Templar) + return true; + break; + case 2517: + if (shrine.shrineType == ShrineType.Warlock) + return true; + break; + case 2518: + if (shrine.shrineType == ShrineType.Warrior) + return true; + break; + case 2519: + if (shrine.shrineType == ShrineType.Priest) + return true; + break; + case 2520: + if (shrine.shrineType == ShrineType.Thief) + return true; + break; + case 2521: + if (shrine.shrineType == ShrineType.Wizard) + return true; + break; + case 2523: + if (shrine.shrineType == ShrineType.Doomsayer) + return true; + break; + case 2524: + if (shrine.shrineType == ShrineType.Sentinel) + return true; + break; + case 2525: + if (shrine.shrineType == ShrineType.Necromancer) + return true; + break; + case 2526: + if (shrine.shrineType == ShrineType.Nightstalker) + return true; + break; + } + + return false; + } + + public static ShrineType getShrineTypeByBlueprintUUID(int blueprintUUID) { + + for (ShrineType shrineType : ShrineType.values()) { + + if (shrineType.getBlueprintUUID() == blueprintUUID) + return shrineType; + } + return null; + } + + public static void RemoveShrineFromCacheByBuilding(Building building) { + + if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) { + Shrine shrine = Shrine.shrinesByBuildingUUID.get(building.getObjectUUID()); + + if (shrine != null) { + shrine.shrineType.RemoveShrineFromServerList(shrine); + Shrine.shrinesByBuildingUUID.remove(building.getObjectUUID()); + DbManager.removeFromCache(Enum.GameObjectType.Shrine, + shrine.getObjectUUID()); + } + } + + } + + public void decay() { + + if (this.getFavors() == 0) + return; + + int decayAmount = (int) (this.getFavors() - (this.getFavors() * .10f)); + + if (decayAmount < 0) + decayAmount = 0; + + if (!DbManager.ShrineQueries.updateFavors(this, decayAmount, this.getFavors())) { + Logger.error("Shrine Decay", "Error writing to DB. UUID: " + this.getObjectUUID()); + return; + } + this.favors = decayAmount; + + Logger.info(shrineType.name() + " uuid:" + this.getObjectUUID() + " Amount: " + this.getFavors() * .10f); + + } + + public synchronized boolean addFavor(PlayerCharacter boonOwner, Item boonItem) { + + if (boonOwner == null) + return false; + + if (boonItem == null) + return false; + + ItemBase ib = boonItem.getItemBase(); + + if (ib == null) + return false; + + if (!boonOwner.getCharItemManager().doesCharOwnThisItem(boonItem.getObjectUUID())) + return false; + + ArrayList boonList = Boon.GetBoonsForItemBase.get(ib.getUUID()); + + if (boonList == null) + return false; + + for (Boon boon : boonList) { + + ShrineType boonShrineType = boon.getShrineType(); + + if (boonShrineType != shrineType) + continue; + + //Same Shrine Type, add favors and stop loop. + int amount = boon.getAmount() * boonItem.getNumOfItems(); + int oldAmount = this.favors; + + if (!DbManager.ShrineQueries.updateFavors(this, this.favors + amount, oldAmount)) { + ChatManager.chatSystemError(boonOwner, "Failed to add boon to shrine."); + return false; + } + + this.favors += amount; + boonOwner.getCharItemManager().delete(boonItem); + boonOwner.getCharItemManager().updateInventory(); + return true; + } + return false; + } + + public synchronized boolean takeFavor(PlayerCharacter boonOwner) { + + if (boonOwner == null) + return false; + + int oldAmount = this.favors; + int newAmount = this.favors - 1; + + if (!DbManager.ShrineQueries.updateFavors(this, newAmount, oldAmount)) { + ChatManager.chatSystemError(boonOwner, "Failed to add boon to shrine."); + return false; + } + this.favors = newAmount; + return true; + } + + @Override + public int compareTo(Shrine other) { + return other.favors.compareTo(this.favors); + } + + public int getRank() { + return shrineType.getShrinesCopy().indexOf(this); + } + + public ShrineType getShrineType() { + return shrineType; + } + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + + } + + public int getFavors() { + return favors; + } + + public void setFavors(Integer favors) { + this.favors = favors; + } + + public int getBuildingID() { + return buildingID; + } + + @Override + public void runAfterLoad() { + // TODO Auto-generated method stub + + } + + @Override + public void removeFromCache() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/engine/objects/SkillReq.java b/src/engine/objects/SkillReq.java new file mode 100644 index 00000000..703279bf --- /dev/null +++ b/src/engine/objects/SkillReq.java @@ -0,0 +1,100 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + + +public class SkillReq extends AbstractGameObject { + + + private int skillID; + private short level; + private ArrayList skillReqs; + + /* This shouldn't be used + public SkillReq(SkillsBase skillsBase, short level, ArrayListskillReqs) { + + super(); + this.skillsBase = skillsBase; + this.level = level; + this.skillReqs = skillReqs; + } + */ + + /* This shouldn't be used + public SkillReq(SkillsBase skillsBase, short level, ArrayListskillReqs, int newUUID) { + + super(newUUID); + this.skillsBase = skillsBase; + this.level = level; + this.skillReqs = skillReqs; + } + */ + + /* This shouldn't be used + public SkillReq(SkillReq a, int newUUID) { + super(a, newUUID); + this.skillsBase = a.skillsBase; + this.level = a.level; + this.skillReqs = a.skillReqs; + } + */ + + /** + * ResultSet Constructor + */ + public SkillReq(ResultSet rs) throws SQLException { + super(rs, 0); + this.skillID = rs.getInt("skillID"); + this.level = rs.getShort("level"); + skillReqs = new ArrayList<>(0); + + int skillReq; + skillReq = rs.getInt("skillreq1"); + if (skillReq > 0) + skillReqs.add((byte) skillReq); + skillReq = rs.getInt("skillreq2"); + if (skillReq > 0) + skillReqs.add((byte) skillReq); + skillReq = rs.getInt("skillreq3"); + if (skillReq > 0) + skillReqs.add((byte) skillReq); + } + + /* + * Getters + */ + public SkillsBase getSkillsBase() { + return DbManager.SkillsBaseQueries.GET_BASE(this.skillID); + } + + public int getSkillID() { + return this.skillID; + } + + public short getLevel() { + return this.level; + } + + public ArrayList getSkillReqs() { + return this.skillReqs; + } + + + @Override + public void updateDatabase() { + + } +} \ No newline at end of file diff --git a/src/engine/objects/SkillsBase.java b/src/engine/objects/SkillsBase.java new file mode 100644 index 00000000..d615b717 --- /dev/null +++ b/src/engine/objects/SkillsBase.java @@ -0,0 +1,158 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.SourceType; +import engine.gameManager.DbManager; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + + +public class SkillsBase extends AbstractGameObject { + + public static ConcurrentHashMap skillsCache = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public static ConcurrentHashMap tokenCache = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + public static HashMap> runeSkillsCache = new HashMap<>(); + private final String name; + private final String nameNoSpace; + private final String description; + private final int token; + private final short strMod; + private final short dexMod; + private final short conMod; + private final short intMod; + private final short spiMod; + public SourceType sourceType; + + /** + * No Table ID Constructor + */ + public SkillsBase(String name, String description, int token, short strMod, + short dexMod, short conMod, short intMod, short spiMod) { + super(); + this.name = name; + this.nameNoSpace = name.replace(" ", ""); + this.sourceType = SourceType.GetSourceType(this.nameNoSpace.replace(",", "")); + this.description = description; + this.token = token; + this.strMod = strMod; + this.dexMod = dexMod; + this.conMod = conMod; + this.intMod = intMod; + this.spiMod = spiMod; + } + + /** + * Normal Constructor + */ + public SkillsBase(String name, String description, int token, short strMod, + short dexMod, short conMod, short intMod, short spiMod, int newUUID) { + super(newUUID); + this.name = name; + this.nameNoSpace = name.replace(" ", ""); + this.description = description; + this.token = token; + this.strMod = strMod; + this.dexMod = dexMod; + this.conMod = conMod; + this.intMod = intMod; + this.spiMod = spiMod; + } + + /** + * ResultSet Constructor + */ + public SkillsBase(ResultSet rs) throws SQLException { + super(rs); + + this.name = rs.getString("name"); + this.nameNoSpace = name.replace(" ", ""); + this.description = rs.getString("description"); + this.sourceType = SourceType.GetSourceType(this.nameNoSpace.replace("-", "").replace("\"", "").replace(",", "")); + this.token = rs.getInt("token"); + this.strMod = rs.getShort("strMod"); + this.dexMod = rs.getShort("dexMod"); + this.conMod = rs.getShort("conMod"); + this.intMod = rs.getShort("intMod"); + this.spiMod = rs.getShort("spiMod"); + } + + public static SkillsBase getFromCache(String name) { + if (skillsCache.containsKey(name)) + return skillsCache.get(name); + else + return null; + } + + public static SkillsBase getFromCache(int token) { + if (tokenCache.containsKey(token)) + return tokenCache.get(token); + else + return null; + } + + public static void putInCache(SkillsBase sb) { + + if (sb == null) + return; + + DbManager.addToCache(sb); + skillsCache.putIfAbsent(sb.name, sb); + tokenCache.putIfAbsent(sb.token, sb); + } + + /* + * Getters + */ + public String getName() { + return name; + } + + public String getNameNoSpace() { + return nameNoSpace; + } + + public String getDescription() { + return description; + } + + public int getToken() { + return this.token; + } + + public short getStrMod() { + return this.strMod; + } + + public short getDexMod() { + return this.dexMod; + } + + public short getConMod() { + return this.conMod; + } + + public short getIntMod() { + return this.intMod; + } + + public short getSpiMod() { + return this.spiMod; + } + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + } +} diff --git a/src/engine/objects/SkillsBaseAttribute.java b/src/engine/objects/SkillsBaseAttribute.java new file mode 100644 index 00000000..4269486e --- /dev/null +++ b/src/engine/objects/SkillsBaseAttribute.java @@ -0,0 +1,70 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class SkillsBaseAttribute extends AbstractGameObject { + + private short attributeID; + private short modValue; + + /** + * No Table ID Constructor + */ + public SkillsBaseAttribute(short attributeID, short modValue) { + super(); + + this.attributeID = attributeID; + this.modValue = modValue; + } + + /** + * Normal Constructor + */ + public SkillsBaseAttribute(short attributeID, short modValue, int newUUID) { + super(newUUID); + + this.attributeID = attributeID; + this.modValue = modValue; + } + + /** + * ResultSet Constructor + */ + public SkillsBaseAttribute(ResultSet rs) throws SQLException { + super(rs); + + this.attributeID = rs.getShort("attributeID"); + this.modValue = rs.getShort("modValue"); + } + + /* + * Getters + */ + public short getAttributeID() { + return attributeID; + } + + public short getModValue() { + return modValue; + } + + + /* + * Database + */ + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + } +} diff --git a/src/engine/objects/StaticColliders.java b/src/engine/objects/StaticColliders.java new file mode 100644 index 00000000..8c2306e5 --- /dev/null +++ b/src/engine/objects/StaticColliders.java @@ -0,0 +1,97 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +public class StaticColliders { + + public static HashMap> _staticColliders = new HashMap<>(); + private int meshID; + private float startX; + private float startY; + private float endX; + private float endY; + private int doorID; + private boolean link = false; + + + /** + * ResultSet Constructor + */ + + public StaticColliders(ResultSet rs) throws SQLException { + this.meshID = rs.getInt("meshID"); + this.startX = rs.getInt("startX"); + this.startY = rs.getInt("startY"); + this.endX = rs.getInt("endX"); + this.endY = rs.getInt("endY"); + this.doorID = rs.getInt("doorID"); + this.link = rs.getBoolean("link"); + } + + public StaticColliders(int meshID, float startX, float startY, float endX, + float endY, int doorID, boolean link) { + super(); + this.meshID = meshID; + this.startX = startX; + this.startY = startY; + this.endX = endX; + this.endY = endY; + this.doorID = doorID; + this.link = link; + } + + public static void loadAllStaticColliders() { + _staticColliders = DbManager.BuildingQueries.LOAD_ALL_STATIC_COLLIDERS(); + } + + public static ArrayList GetStaticCollidersForMeshID(int meshID) { + return _staticColliders.get(meshID); + } + + + public int getMeshID() { + return meshID; + } + + public float getStartX() { + return startX; + } + + public float getStartY() { + return startY; + } + + public float getEndX() { + return endX; + } + + public float getEndY() { + return endY; + } + + public int getDoorID() { + return doorID; + } + + public boolean isLink() { + return link; + } + + public void setLink(boolean link) { + this.link = link; + } +} diff --git a/src/engine/objects/Transaction.java b/src/engine/objects/Transaction.java new file mode 100644 index 00000000..1e282195 --- /dev/null +++ b/src/engine/objects/Transaction.java @@ -0,0 +1,108 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum.GameObjectType; +import engine.Enum.TransactionType; +import org.joda.time.DateTime; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Date; + + +public class Transaction implements Comparable { + + private final int warehouseUUID; + private final int targetUUID; + private final Resource resource; + private final DateTime date; + private final int amount; + private final TransactionType transactionType; + private GameObjectType targetType; + + + public Transaction(ResultSet rs) throws SQLException { + this.warehouseUUID = rs.getInt("warehouseUID"); + this.targetUUID = rs.getInt("targetUID"); + this.targetType = GameObjectType.valueOf(rs.getString("targetType")); + this.transactionType = TransactionType.valueOf(rs.getString("type").toUpperCase()); + this.resource = Resource.valueOf(rs.getString("resource").toUpperCase()); + this.amount = rs.getInt("amount"); + + Date sqlDateTime = rs.getTimestamp("date"); + + if (sqlDateTime != null) + this.date = new DateTime(sqlDateTime); + else + this.date = DateTime.now(); + + } + + + public Transaction(int warehouseUUID, GameObjectType targetType, int targetUUID, TransactionType transactionType, Resource resource, int amount, + DateTime date) { + this.warehouseUUID = warehouseUUID; + this.targetUUID = targetUUID; + this.resource = resource; + this.date = date; + this.amount = amount; + this.targetType = targetType; + this.transactionType = transactionType; + } + + + public int getWarehouseUUID() { + return warehouseUUID; + } + + + public int getTargetUUID() { + return targetUUID; + } + + + public Resource getResource() { + return resource; + } + + + public DateTime getDate() { + return date; + } + + + public int getAmount() { + return amount; + } + + + public TransactionType getTransactionType() { + return transactionType; + } + + + @Override + public int compareTo(Transaction arg0) { + // TODO Auto-generated method stub + return 0; + } + + + public GameObjectType getTargetType() { + return targetType; + } + + + public void setTargetType(GameObjectType targetType) { + this.targetType = targetType; + } + +} diff --git a/src/engine/objects/VendorDialog.java b/src/engine/objects/VendorDialog.java new file mode 100644 index 00000000..674f34ea --- /dev/null +++ b/src/engine/objects/VendorDialog.java @@ -0,0 +1,74 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.gameManager.DbManager; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + + +public class VendorDialog extends AbstractGameObject { + + private static VendorDialog vd; + private final String dialogType; + private final String intro; + private ArrayList options = new ArrayList<>(); + + public VendorDialog(String dialogType, String intro, int UUID) { + super(UUID); + this.dialogType = dialogType; + this.intro = intro; + } + + /** + * ResultSet Constructor + */ + public VendorDialog(ResultSet rs) throws SQLException { + super(rs); + this.dialogType = rs.getString("dialogType"); + this.intro = rs.getString("intro"); + this.options = DbManager.MenuQueries.GET_MENU_OPTIONS(this.getObjectUUID()); + } + + public static VendorDialog getHostileVendorDialog() { + if (VendorDialog.vd == null) + VendorDialog.vd = new VendorDialog("TrainerDialog", "HostileIntro", 0); + return VendorDialog.vd; + } + + public static VendorDialog getVendorDialog(int id) { + + return DbManager.VendorDialogQueries.GET_VENDORDIALOG(id); + } + + /* + * Getters + */ + public String getDialogType() { + return this.dialogType; + } + + public String getIntro() { + return this.intro; + } + + public ArrayList getOptions() { + return this.options; + } + + /* + * Database + */ + @Override + public void updateDatabase() { + } +} diff --git a/src/engine/objects/Warehouse.java b/src/engine/objects/Warehouse.java new file mode 100644 index 00000000..f96a5fcb --- /dev/null +++ b/src/engine/objects/Warehouse.java @@ -0,0 +1,1337 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import ch.claude_martin.enumbitset.EnumBitSet; +import engine.Enum; +import engine.Enum.*; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; +import engine.gameManager.DbManager; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.*; +import engine.server.MBServerStatics; +import org.joda.time.DateTime; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class Warehouse extends AbstractWorldObject { + + + public static ItemBase goldIB = ItemBase.getItemBase(7); + public static ItemBase stoneIB = ItemBase.getItemBase(1580000); + public static ItemBase truesteelIB = ItemBase.getItemBase(1580001); + public static ItemBase ironIB = ItemBase.getItemBase(1580002); + public static ItemBase adamantIB = ItemBase.getItemBase(1580003); + public static ItemBase lumberIB = ItemBase.getItemBase(1580004); + public static ItemBase oakIB = ItemBase.getItemBase(1580005); + public static ItemBase bronzewoodIB = ItemBase.getItemBase(1580006); + public static ItemBase mandrakeIB = ItemBase.getItemBase(1580007); + public static ItemBase coalIB = ItemBase.getItemBase(1580008); + public static ItemBase agateIB = ItemBase.getItemBase(1580009); + public static ItemBase diamondIB = ItemBase.getItemBase(1580010); + public static ItemBase onyxIB = ItemBase.getItemBase(1580011); + public static ItemBase azothIB = ItemBase.getItemBase(1580012); + public static ItemBase orichalkIB = ItemBase.getItemBase(1580013); + public static ItemBase antimonyIB = ItemBase.getItemBase(1580014); + public static ItemBase sulferIB = ItemBase.getItemBase(1580015); + public static ItemBase quicksilverIB = ItemBase.getItemBase(1580016); + public static ItemBase galvorIB = ItemBase.getItemBase(1580017); + public static ItemBase wormwoodIB = ItemBase.getItemBase(1580018); + public static ItemBase obsidianIB = ItemBase.getItemBase(1580019); + public static ItemBase bloodstoneIB = ItemBase.getItemBase(1580020); + public static ItemBase mithrilIB = ItemBase.getItemBase(1580021); + public static ConcurrentHashMap maxResources = new ConcurrentHashMap<>(); + public static ConcurrentHashMap warehouseByBuildingUUID = new ConcurrentHashMap<>(); + public EnumBitSet lockedResourceTypes; + private int UID; + private int buildingUID; + private ArrayList transactions = new ArrayList<>(); + private ConcurrentHashMap resources = new ConcurrentHashMap<>(); + + + /** + * ResultSet Constructor + */ + public Warehouse(ResultSet rs) throws SQLException { + super(rs); + this.UID = rs.getInt("UID"); + this.resources.put(stoneIB, rs.getInt("warehouse_stone")); + this.resources.put(truesteelIB, rs.getInt("warehouse_truesteel")); + this.resources.put(ironIB, rs.getInt("warehouse_iron")); + this.resources.put(adamantIB, rs.getInt("warehouse_adamant")); + this.resources.put(lumberIB, rs.getInt("warehouse_lumber")); + this.resources.put(oakIB, rs.getInt("warehouse_oak")); + this.resources.put(bronzewoodIB, rs.getInt("warehouse_bronzewood")); + this.resources.put(mandrakeIB, rs.getInt("warehouse_mandrake")); + this.resources.put(coalIB, rs.getInt("warehouse_coal")); + this.resources.put(agateIB, rs.getInt("warehouse_agate")); + this.resources.put(diamondIB, rs.getInt("warehouse_diamond")); + this.resources.put(onyxIB, rs.getInt("warehouse_onyx")); + this.resources.put(azothIB, rs.getInt("warehouse_azoth")); + this.resources.put(orichalkIB, rs.getInt("warehouse_orichalk")); + this.resources.put(antimonyIB, rs.getInt("warehouse_antimony")); + this.resources.put(sulferIB, rs.getInt("warehouse_sulfur")); + this.resources.put(quicksilverIB, rs.getInt("warehouse_quicksilver")); + this.resources.put(galvorIB, rs.getInt("warehouse_galvor")); + this.resources.put(wormwoodIB, rs.getInt("warehouse_wormwood")); + this.resources.put(obsidianIB, rs.getInt("warehouse_obsidian")); + this.resources.put(bloodstoneIB, rs.getInt("warehouse_bloodstone")); + this.resources.put(mithrilIB, rs.getInt("warehouse_mithril")); + this.resources.put(goldIB, rs.getInt("warehouse_gold")); + this.lockedResourceTypes = EnumBitSet.asEnumBitSet(rs.getLong("warehouse_locks"), Enum.ResourceType.class); + this.buildingUID = rs.getInt("parent"); + Warehouse.warehouseByBuildingUUID.put(this.buildingUID, this); + } + + public static ConcurrentHashMap getMaxResources() { + if (maxResources.size() != 23) { + maxResources.put(7, 100000000); + maxResources.put(1580000, 10000); + maxResources.put(1580001, 2000); + maxResources.put(1580002, 2000); + maxResources.put(1580003, 1000); + maxResources.put(1580004, 10000); + maxResources.put(1580005, 3000); + maxResources.put(1580006, 3000); + maxResources.put(1580007, 1000); + maxResources.put(1580008, 3000); + maxResources.put(1580009, 2000); + maxResources.put(1580010, 2000); + maxResources.put(1580011, 1000); + maxResources.put(1580012, 2000); + maxResources.put(1580013, 3000); + maxResources.put(1580014, 1000); + maxResources.put(1580015, 1000); + maxResources.put(1580016, 1000); + maxResources.put(1580017, 500); + maxResources.put(1580018, 500); + maxResources.put(1580019, 500); + maxResources.put(1580020, 500); + maxResources.put(1580021, 500); + } + + return maxResources; + } + + public static void warehouseDeposit(MerchantMsg msg, PlayerCharacter player, NPC npc, ClientConnection origin) { + + Building warehouseBuilding; + Warehouse warehouse; + int depositAmount; + Dispatch dispatch; + + Item resource = Item.getFromCache(msg.getItemID()); + + if (resource == null) + return; + + depositAmount = msg.getAmount(); + CharacterItemManager itemMan = player.getCharItemManager(); + + if (itemMan.doesCharOwnThisItem(resource.getObjectUUID()) == false) + return; + + warehouseBuilding = npc.getBuilding(); + + if (warehouseBuilding == null) + return; + + warehouse = warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID()); + + if (warehouse == null) + return; + + ItemBase ib = resource.getItemBase(); + + if (!warehouse.deposit(player, resource, depositAmount, true, true)) { + // ChatManager.chatGuildError(player, "Failed to deposit " + ib.getName() +"."); + // Logger.debug("OpenWindow", player.getName() + " Failed to deposit Item with ID " + resource.getObjectUUID() + " from Warehouse With ID = " + warehouseBuilding.getObjectUUID()); + return; + } + + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + vrm.setGuild(player.getGuild()); + vrm.setWarehouseBuilding(warehouseBuilding); + vrm.configure(); + dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + public static void warehouseWithdraw(MerchantMsg msg, PlayerCharacter player, NPC npc, ClientConnection origin) { + + int withdrawAmount; + Building warehouseBuilding; + Warehouse warehouse; + Dispatch dispatch; + + withdrawAmount = msg.getAmount(); + warehouseBuilding = npc.getBuilding(); + + if (warehouseBuilding == null) + return; + + if (player.getGuild() != warehouseBuilding.getGuild() || GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) + return; + + warehouse = warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID()); + + if (warehouse == null) + return; + + int hashID = msg.getHashID(); + int itemBaseID = ItemBase.getItemHashIDMap().get(hashID); + ItemBase ib = ItemBase.getItemBase(itemBaseID); + + if (ib == null) { + Logger.debug("Failed to find Resource ItemBaseID with Hash ID = " + hashID); + return; + } + + if (warehouse.isResourceLocked(ib) == true) { + ChatManager.chatSystemInfo(player, "You cannot withdrawl a locked resource."); + return; + } + if (!warehouse.withdraw(player, ib, withdrawAmount, true, true)) { + ChatManager.chatGuildError(player, "Failed to withdrawl " + ib.getName() + '.'); + Logger.debug(player.getName() + " Failed to withdrawl =" + ib.getName() + " from Warehouse With ID = " + warehouseBuilding.getObjectUUID()); + return; + } + + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + vrm.setGuild(player.getGuild()); + vrm.setWarehouseBuilding(warehouseBuilding); + vrm.configure(); + dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + public static void warehouseLock(MerchantMsg msg, PlayerCharacter player, NPC npc, ClientConnection origin) { + Building warehouse; + int hashID; + Dispatch dispatch; + + hashID = msg.getHashID(); + warehouse = npc.getBuilding(); + + if (warehouse == null) + return; + + if (player.getGuild() != warehouse.getGuild() || GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) + return; + + Warehouse wh = warehouseByBuildingUUID.get(warehouse.getObjectUUID()); + + if (wh == null) + return; + + int itemBaseID = ItemBase.getItemHashIDMap().get(hashID); + ItemBase ib = ItemBase.getItemBase(itemBaseID); + + if (ib == null) + return; + + if (wh.isResourceLocked(ib) == true) { + boolean worked = false; + EnumBitSet bitSet = EnumBitSet.asEnumBitSet(wh.lockedResourceTypes.toLong(), ResourceType.class); + + bitSet.remove(ResourceType.resourceLookup.get(itemBaseID)); + + worked = DbManager.WarehouseQueries.updateLocks(wh, bitSet.toLong()); + + if (worked) { + wh.lockedResourceTypes.remove(Enum.ResourceType.resourceLookup.get(itemBaseID)); + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + vrm.setGuild(player.getGuild()); + vrm.setWarehouseBuilding(warehouse); + vrm.configure(); + dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + return; + } + + EnumBitSet bitSet = EnumBitSet.asEnumBitSet(wh.lockedResourceTypes.toLong(), ResourceType.class); + + bitSet.add(ResourceType.resourceLookup.get(itemBaseID)); + + if (DbManager.WarehouseQueries.updateLocks(wh, bitSet.toLong()) == false) + return; + + wh.lockedResourceTypes.add(Enum.ResourceType.resourceLookup.get(itemBaseID)); + ViewResourcesMessage vrm = new ViewResourcesMessage(player); + vrm.setGuild(player.getGuild()); + vrm.setWarehouseBuilding(warehouse); + vrm.configure(); + dispatch = Dispatch.borrow(player, vrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + public ConcurrentHashMap getResources() { + return resources; + } + + public int getUID() { + return UID; + } + + public void setUID(int uID) { + UID = uID; + } + + public synchronized boolean deposit(PlayerCharacter pc, Item resource, int amount, boolean removeFromInventory, boolean transaction) { + + ClientConnection origin = pc.getClientConnection(); + if (origin == null) + return false; + + if (amount < 0) { + Logger.info(pc.getFirstName() + " Attempting to Dupe!!!!!!"); + return false; + } + + ItemBase ib = resource.getItemBase(); + + if (ib == null) + return false; + + if (this.resources.get(ib) == null) + return false; + + CharacterItemManager itemMan = pc.getCharItemManager(); + + if (itemMan == null) + return false; + + + if (itemMan.getGoldTrading() > 0) { + ErrorPopupMsg.sendErrorPopup(pc, 195); + return false; + } + + + if (!itemMan.doesCharOwnThisItem(resource.getObjectUUID())) + return false; + + if (!resource.validForInventory(origin, pc, itemMan)) + return false; + + if (resource.getNumOfItems() < amount) + return false; + + int oldAmount = resources.get(ib); + + int newAmount = oldAmount + amount; + + if (newAmount > Warehouse.getMaxResources().get(ib.getUUID())) { + //ChatManager.chatSystemInfo(pc, "The Warehouse is at it's maximum for this type of resource."); + return false; + } + + + if (removeFromInventory) { + if (ib.getUUID() == 7) { + + if (itemMan.getGoldInventory().getNumOfItems() - amount < 0) + return false; + + if (itemMan.getGoldInventory().getNumOfItems() - amount > MBServerStatics.PLAYER_GOLD_LIMIT) + return false; + + if (!itemMan.modifyInventoryGold(-amount)) { + //ChatManager.chatSystemError(pc, "You do not have this Gold."); + return false; + } + + UpdateGoldMsg ugm = new UpdateGoldMsg(pc); + ugm.configure(); + Dispatch dispatch = Dispatch.borrow(pc, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + itemMan.updateInventory(); + + } else { + itemMan.delete(resource); + itemMan.updateInventory(); + } + } + itemMan.updateInventory(); + int itemID = ib.getUUID(); + boolean worked = false; + switch (itemID) { + case 7: + worked = DbManager.WarehouseQueries.updateGold(this, newAmount); + break; + case 1580000: + worked = DbManager.WarehouseQueries.updateStone(this, newAmount); + break; + case 1580001: + worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount); + break; + case 1580002: + worked = DbManager.WarehouseQueries.updateIron(this, newAmount); + break; + case 1580003: + worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount); + break; + case 1580004: + worked = DbManager.WarehouseQueries.updateLumber(this, newAmount); + break; + case 1580005: + worked = DbManager.WarehouseQueries.updateOak(this, newAmount); + break; + case 1580006: + worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount); + break; + case 1580007: + worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount); + break; + case 1580008: + worked = DbManager.WarehouseQueries.updateCoal(this, newAmount); + break; + case 1580009: + worked = DbManager.WarehouseQueries.updateAgate(this, newAmount); + break; + case 1580010: + worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount); + break; + case 1580011: + worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount); + break; + case 1580012: + worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount); + break; + case 1580013: + worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount); + break; + case 1580014: + worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount); + break; + case 1580015: + worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount); + break; + case 1580016: + worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount); + break; + case 1580017: + worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount); + break; + case 1580018: + worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount); + break; + case 1580019: + worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount); + break; + case 1580020: + worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount); + break; + case 1580021: + worked = DbManager.WarehouseQueries.updateMithril(this, newAmount); + break; + } + + if (!worked) + return false; + + resources.put(ib, newAmount); + + Resource resourceType; + + if (resource.getItemBase().getType().equals(engine.Enum.ItemType.GOLD)) + resourceType = Resource.GOLD; + else + resourceType = Resource.valueOf(resource.getItemBase().getName().toUpperCase()); + + if (transaction) + this.AddTransactionToWarehouse(pc.getObjectType(), pc.getObjectUUID(), TransactionType.DEPOSIT, resourceType, amount); + + return true; + } + + //for mine deposit + public synchronized boolean depositFromMine(Mine mine, ItemBase resource, int amount) { + + if (resource == null) + return false; + + if (this.resources.get(resource) == null) + return false; + + int oldAmount = resources.get(resource); + int newAmount = oldAmount + amount; + + if (newAmount > Warehouse.getMaxResources().get(resource.getUUID())) + return false; + + int itemID = resource.getUUID(); + boolean worked = false; + + switch (itemID) { + case 7: + worked = DbManager.WarehouseQueries.updateGold(this, newAmount); + break; + case 1580000: + worked = DbManager.WarehouseQueries.updateStone(this, newAmount); + break; + case 1580001: + worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount); + break; + case 1580002: + worked = DbManager.WarehouseQueries.updateIron(this, newAmount); + break; + case 1580003: + worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount); + break; + case 1580004: + worked = DbManager.WarehouseQueries.updateLumber(this, newAmount); + break; + case 1580005: + worked = DbManager.WarehouseQueries.updateOak(this, newAmount); + break; + case 1580006: + worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount); + break; + case 1580007: + worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount); + break; + case 1580008: + worked = DbManager.WarehouseQueries.updateCoal(this, newAmount); + break; + case 1580009: + worked = DbManager.WarehouseQueries.updateAgate(this, newAmount); + break; + case 1580010: + worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount); + break; + case 1580011: + worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount); + break; + case 1580012: + worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount); + break; + case 1580013: + worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount); + break; + case 1580014: + worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount); + break; + case 1580015: + worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount); + break; + case 1580016: + worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount); + break; + case 1580017: + worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount); + break; + case 1580018: + worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount); + break; + case 1580019: + worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount); + break; + case 1580020: + worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount); + break; + case 1580021: + worked = DbManager.WarehouseQueries.updateMithril(this, newAmount); + break; + } + if (!worked) + return false; + + this.resources.put(resource, newAmount); + Resource resourceType; + + if (resource.getUUID() == 7) + resourceType = Resource.GOLD; + else + resourceType = Resource.valueOf(resource.getName().toUpperCase()); + + if (mine != null) + this.AddTransactionToWarehouse(GameObjectType.Building, mine.getBuildingID(), TransactionType.MINE, resourceType, amount); + + return true; + } + + public synchronized boolean depositRealmTaxes(PlayerCharacter taxer, ItemBase ib, int amount) { + + if (ib == null) + return false; + + if (this.resources.get(ib) == null) + return false; + + int oldAmount = resources.get(ib); + int newAmount = oldAmount + amount; + + if (newAmount > Warehouse.getMaxResources().get(ib.getUUID())) + return false; + + int itemID = ib.getUUID(); + boolean worked = false; + + switch (itemID) { + case 7: + worked = DbManager.WarehouseQueries.updateGold(this, newAmount); + break; + case 1580000: + worked = DbManager.WarehouseQueries.updateStone(this, newAmount); + break; + case 1580001: + worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount); + break; + case 1580002: + worked = DbManager.WarehouseQueries.updateIron(this, newAmount); + break; + case 1580003: + worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount); + break; + case 1580004: + worked = DbManager.WarehouseQueries.updateLumber(this, newAmount); + break; + case 1580005: + worked = DbManager.WarehouseQueries.updateOak(this, newAmount); + break; + case 1580006: + worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount); + break; + case 1580007: + worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount); + break; + case 1580008: + worked = DbManager.WarehouseQueries.updateCoal(this, newAmount); + break; + case 1580009: + worked = DbManager.WarehouseQueries.updateAgate(this, newAmount); + break; + case 1580010: + worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount); + break; + case 1580011: + worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount); + break; + case 1580012: + worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount); + break; + case 1580013: + worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount); + break; + case 1580014: + worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount); + break; + case 1580015: + worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount); + break; + case 1580016: + worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount); + break; + case 1580017: + worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount); + break; + case 1580018: + worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount); + break; + case 1580019: + worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount); + break; + case 1580020: + worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount); + break; + case 1580021: + worked = DbManager.WarehouseQueries.updateMithril(this, newAmount); + break; + } + + if (!worked) + return false; + + this.resources.put(ib, newAmount); + Resource resourceType; + + if (ib.getUUID() == 7) + resourceType = Resource.GOLD; + else + resourceType = Resource.valueOf(ib.getName().toUpperCase()); + + this.AddTransactionToWarehouse(taxer.getObjectType(), taxer.getObjectUUID(), TransactionType.TAXRESOURCEDEPOSIT, resourceType, amount); + + return true; + } + + public synchronized boolean depositProfitTax(ItemBase ib, int amount, Building building) { + + if (ib == null) + return false; + + if (this.resources.get(ib) == null) + return false; + + int oldAmount = resources.get(ib); + int newAmount = oldAmount + amount; + + if (newAmount > Warehouse.getMaxResources().get(ib.getUUID())) + return false; + + int itemID = ib.getUUID(); + boolean worked = false; + + switch (itemID) { + case 7: + worked = DbManager.WarehouseQueries.updateGold(this, newAmount); + break; + case 1580000: + worked = DbManager.WarehouseQueries.updateStone(this, newAmount); + break; + case 1580001: + worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount); + break; + case 1580002: + worked = DbManager.WarehouseQueries.updateIron(this, newAmount); + break; + case 1580003: + worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount); + break; + case 1580004: + worked = DbManager.WarehouseQueries.updateLumber(this, newAmount); + break; + case 1580005: + worked = DbManager.WarehouseQueries.updateOak(this, newAmount); + break; + case 1580006: + worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount); + break; + case 1580007: + worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount); + break; + case 1580008: + worked = DbManager.WarehouseQueries.updateCoal(this, newAmount); + break; + case 1580009: + worked = DbManager.WarehouseQueries.updateAgate(this, newAmount); + break; + case 1580010: + worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount); + break; + case 1580011: + worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount); + break; + case 1580012: + worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount); + break; + case 1580013: + worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount); + break; + case 1580014: + worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount); + break; + case 1580015: + worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount); + break; + case 1580016: + worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount); + break; + case 1580017: + worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount); + break; + case 1580018: + worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount); + break; + case 1580019: + worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount); + break; + case 1580020: + worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount); + break; + case 1580021: + worked = DbManager.WarehouseQueries.updateMithril(this, newAmount); + break; + } + + if (!worked) + return false; + + this.resources.put(ib, newAmount); + Resource resourceType; + + if (ib.getUUID() == 7) + resourceType = Resource.GOLD; + else + resourceType = Resource.valueOf(ib.getName().toUpperCase()); + + if (building != null) + this.AddTransactionToWarehouse(GameObjectType.Building, building.getObjectUUID(), TransactionType.DEPOSIT, resourceType, amount); + + return true; + } + + public synchronized boolean withdraw(PlayerCharacter pc, ItemBase ib, int amount, boolean addToInventory, boolean transaction) { + + if (pc == null) + return false; + + if (ib == null) + return false; + + if (this.resources.get(ib) == null) + return false; + + if (amount <= 0) + return false; + + CharacterItemManager itemMan = pc.getCharItemManager(); + + if (itemMan == null) + return false; + + if (addToInventory) + if (!itemMan.hasRoomInventory(ib.getWeight())) { + ChatManager.chatSystemInfo(pc, "You can not carry any more of that item."); + return false; + } + + if (addToInventory && ib.getUUID() == ItemBase.GOLD_BASE_ID) { + if (pc.getCharItemManager().getGoldInventory().getNumOfItems() + amount > MBServerStatics.PLAYER_GOLD_LIMIT) { + return false; + } + + if (pc.getCharItemManager().getGoldInventory().getNumOfItems() + amount < 0) + return false; + } + int oldAmount = this.resources.get(ib); + + if (oldAmount < amount) + return false; + + int hashID = ib.getHashID(); + int newAmount = oldAmount - amount; + + boolean worked = false; + + switch (hashID) { + case 2308551: + worked = DbManager.WarehouseQueries.updateGold(this, newAmount); + break; + case 74856115: + worked = DbManager.WarehouseQueries.updateStone(this, newAmount); + break; + case -317484979: + worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount); + break; + case 2504297: + worked = DbManager.WarehouseQueries.updateIron(this, newAmount); + break; + case -1741189964: + worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount); + break; + case -1603256692: + worked = DbManager.WarehouseQueries.updateLumber(this, newAmount); + break; + case 74767: + worked = DbManager.WarehouseQueries.updateOak(this, newAmount); + break; + case 1334770447: + worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount); + break; + case 1191391799: + worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount); + break; + case 2559427: + worked = DbManager.WarehouseQueries.updateCoal(this, newAmount); + break; + case 75173057: + worked = DbManager.WarehouseQueries.updateAgate(this, newAmount); + break; + case -1730704107: + worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount); + break; + case 2977263: + worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount); + break; + case 78329697: + worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount); + break; + case -2036290524: + worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount); + break; + case 452320058: + worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount); + break; + case -1586349421: + worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount); + break; + case -472884509: + worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount); + break; + case -1596311545: + worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount); + break; + case 1532478436: + worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount); + break; + case -697973233: + worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount); + break; + case -1569826353: + worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount); + break; + case -1761257186: + worked = DbManager.WarehouseQueries.updateMithril(this, newAmount); + break; + } + if (!worked) + return false; + + this.resources.put(ib, newAmount); + + if (addToInventory) { + if (ib.getUUID() == 7) { + + itemMan.addGoldToInventory(amount, false); + UpdateGoldMsg ugm = new UpdateGoldMsg(pc); + ugm.configure(); + Dispatch dispatch = Dispatch.borrow(pc, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + itemMan.updateInventory(); + } else { + boolean itemWorked = false; + Item item = new Item(ib, pc.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0, + (short) 1, (short) 1, true, false, ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + item.setNumOfItems(amount); + item.containerType = Enum.ItemContainerType.INVENTORY; + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + itemWorked = true; + } catch (Exception e) { + Logger.error(e); + } + if (itemWorked) { + itemMan.addItemToInventory(item); + itemMan.updateInventory(); + } + } + } + Resource resourceType; + + if (ib.getUUID() == 7) + resourceType = Resource.GOLD; + else + resourceType = Resource.valueOf(ib.getName().toUpperCase()); + + if (transaction) + this.AddTransactionToWarehouse(pc.getObjectType(), pc.getObjectUUID(), TransactionType.WITHDRAWL, resourceType, amount); + + return true; + } + + public synchronized boolean withdraw(NPC npc, ItemBase ib, int amount, boolean addToInventory, boolean transaction) { + + if (npc == null) + return false; + + if (ib == null) + return false; + + if (this.resources.get(ib) == null) + return false; + + if (amount <= 0) + return false; + + int oldAmount = this.resources.get(ib); + + if (oldAmount < amount) + return false; + + int hashID = ib.getHashID(); + int newAmount = oldAmount - amount; + boolean worked = false; + + switch (hashID) { + case 2308551: + worked = DbManager.WarehouseQueries.updateGold(this, newAmount); + break; + case 74856115: + worked = DbManager.WarehouseQueries.updateStone(this, newAmount); + break; + case -317484979: + worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount); + break; + case 2504297: + worked = DbManager.WarehouseQueries.updateIron(this, newAmount); + break; + case -1741189964: + worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount); + break; + case -1603256692: + worked = DbManager.WarehouseQueries.updateLumber(this, newAmount); + break; + case 74767: + worked = DbManager.WarehouseQueries.updateOak(this, newAmount); + break; + case 1334770447: + worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount); + break; + case 1191391799: + worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount); + break; + case 2559427: + worked = DbManager.WarehouseQueries.updateCoal(this, newAmount); + break; + case 75173057: + worked = DbManager.WarehouseQueries.updateAgate(this, newAmount); + break; + case -1730704107: + worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount); + break; + case 2977263: + worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount); + break; + case 78329697: + worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount); + break; + case -2036290524: + worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount); + break; + case 452320058: + worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount); + break; + case -1586349421: + worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount); + break; + case -472884509: + worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount); + break; + case -1596311545: + worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount); + break; + case 1532478436: + worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount); + break; + case -697973233: + worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount); + break; + case -1569826353: + worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount); + break; + case -1761257186: + worked = DbManager.WarehouseQueries.updateMithril(this, newAmount); + break; + } + + if (!worked) + return false; + + this.resources.put(ib, newAmount); + Resource resourceType; + + if (ib.getUUID() == 7) + resourceType = Resource.GOLD; + else + resourceType = Resource.valueOf(ib.getName().toUpperCase()); + + if (transaction) + this.AddTransactionToWarehouse(npc.getObjectType(), npc.getObjectUUID(), TransactionType.WITHDRAWL, resourceType, amount); + + return true; + } + + public synchronized boolean transferResources(PlayerCharacter taxer, TaxResourcesMsg msg, ArrayList realmResources, float taxPercent, Warehouse toWarehouse) { + + for (int ibID : realmResources) { + + ItemBase ib = ItemBase.getItemBase(ibID); + + if (ib == null) + return false; + + if (this.resources.get(ib) == null) + return false; + + int amount = (int) (this.resources.get(ib) * taxPercent); + + if (amount <= 0) { + msg.getResources().put(ib.getHashID(), 0); + continue; + } + + int oldAmount = this.resources.get(ib); + + if (oldAmount < amount) + amount = oldAmount; + + int hashID = ib.getHashID(); + int newAmount = oldAmount - amount; + + if (newAmount < amount) + continue; + + boolean worked = false; + + switch (hashID) { + case 2308551: + worked = DbManager.WarehouseQueries.updateGold(this, newAmount); + break; + case 74856115: + worked = DbManager.WarehouseQueries.updateStone(this, newAmount); + break; + case -317484979: + worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount); + break; + case 2504297: + worked = DbManager.WarehouseQueries.updateIron(this, newAmount); + break; + case -1741189964: + worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount); + break; + case -1603256692: + worked = DbManager.WarehouseQueries.updateLumber(this, newAmount); + break; + case 74767: + worked = DbManager.WarehouseQueries.updateOak(this, newAmount); + break; + case 1334770447: + worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount); + break; + case 1191391799: + worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount); + break; + case 2559427: + worked = DbManager.WarehouseQueries.updateCoal(this, newAmount); + break; + case 75173057: + worked = DbManager.WarehouseQueries.updateAgate(this, newAmount); + break; + case -1730704107: + worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount); + break; + case 2977263: + worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount); + break; + case 78329697: + worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount); + break; + case -2036290524: + worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount); + break; + case 452320058: + worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount); + break; + case -1586349421: + worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount); + break; + case -472884509: + worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount); + break; + case -1596311545: + worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount); + break; + case 1532478436: + worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount); + break; + case -697973233: + worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount); + break; + case -1569826353: + worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount); + break; + case -1761257186: + worked = DbManager.WarehouseQueries.updateMithril(this, newAmount); + break; + } + + if (!worked) { + msg.getResources().put(ib.getHashID(), 0); + continue; + } + + msg.getResources().put(ib.getHashID(), amount); + + this.resources.put(ib, newAmount); + toWarehouse.depositRealmTaxes(taxer, ib, amount); + Resource resourceType; + + if (ib.getUUID() == 7) + resourceType = Resource.GOLD; + else + resourceType = Resource.valueOf(ib.getName().toUpperCase()); + + this.AddTransactionToWarehouse(taxer.getObjectType(), taxer.getObjectUUID(), TransactionType.TAXRESOURCE, resourceType, amount); + + } + return true; + } + + public synchronized boolean loot(PlayerCharacter pc, ItemBase ib, int amount, boolean addToInventory) { + + if (pc == null) + return false; + + if (ib == null) + return false; + + if (this.resources.get(ib) == null) + return false; + + if (amount <= 0) + return false; + + CharacterItemManager itemMan = pc.getCharItemManager(); + + if (itemMan == null) + return false; + + if (!itemMan.hasRoomInventory(ib.getWeight())) { + ChatManager.chatSystemInfo(pc, "You can not carry any more of that item."); + return false; + } + + int oldAmount = this.resources.get(ib); + + if (oldAmount < amount) + return false; + + int newAmount = oldAmount - amount; + + this.resources.put(ib, newAmount); + + if (addToInventory) { + if (ib.getUUID() == 7) { + + itemMan.addGoldToInventory(amount, false); + UpdateGoldMsg ugm = new UpdateGoldMsg(pc); + ugm.configure(); + Dispatch dispatch = Dispatch.borrow(pc, ugm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + itemMan.updateInventory(); + } else { + boolean itemWorked = false; + Item item = new Item(ib, pc.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0, + (short) 1, (short) 1, true, false, ItemContainerType.INVENTORY, (byte) 0, + new ArrayList<>(), ""); + item.setNumOfItems(amount); + item.containerType = Enum.ItemContainerType.INVENTORY; + + try { + item = DbManager.ItemQueries.ADD_ITEM(item); + itemWorked = true; + } catch (Exception e) { + Logger.error(e); + } + if (itemWorked) { + itemMan.addItemToInventory(item); + itemMan.updateInventory(); + } + } + } + + return true; + } + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + + } + + @Override + public void runAfterLoad() { + + try { + Building warehouseBuilding = BuildingManager.getBuilding(this.buildingUID); + Logger.info("configuring warehouse " + UID + " for city " + warehouseBuilding.getCity().getCityName() + " structure UUID " + this.buildingUID); + + //Building is gone, but Warehouse still in DB?? Should never happen, sanity check anyway. + if (warehouseBuilding == null) { + Logger.error("Failed to load Building for Warehouse"); + return; + } + + Zone cityZone = warehouseBuilding.getParentZone(); + + if (cityZone == null) { + Logger.error("Failed to load Zone for Warehouse with UUID " + this.getObjectUUID()); + return; + } + + City city = City.getCity(cityZone.getPlayerCityUUID()); + + if (city == null) { + Logger.error("Failed to load City for Warehouse with UUID " + this.getObjectUUID()); + return; + } + + warehouseByBuildingUUID.put(this.buildingUID, this); + city.setWarehouseBuildingID(this.buildingUID); + } catch (Exception E) { + Logger.info(this.getObjectUUID() + " failed"); + + } + } + + public boolean isEmpty() { + int amount = 0; + for (ItemBase ib : ItemBase.getResourceList()) { + if (amount > 0) + return false; + amount += resources.get(ib); + } + return true; + } + + public int getBuildingUID() { + return buildingUID; + } + + public void loadAllTransactions() { + this.transactions = DbManager.WarehouseQueries.GET_TRANSACTIONS_FOR_WAREHOUSE(this.buildingUID); + } + + public boolean AddTransactionToWarehouse(GameObjectType targetType, int targetUUID, TransactionType transactionType, Resource resource, int amount) { + + + if (!DbManager.WarehouseQueries.CREATE_TRANSACTION(this.buildingUID, targetType, targetUUID, transactionType, resource, amount, DateTime.now())) + return false; + + Transaction transaction = new Transaction(this.buildingUID, targetType, targetUUID, transactionType, resource, amount, DateTime.now()); + this.transactions.add(transaction); + return true; + } + + public ArrayList getTransactions() { + return transactions; + } + + public boolean isAboveCap(ItemBase ib, int deposit) { + int newAmount = this.resources.get(ib) + deposit; + return newAmount > Warehouse.getMaxResources().get(ib.getUUID()); + + } + + public boolean isResourceLocked(ItemBase itemBase) { + + Enum.ResourceType resourceType; + + resourceType = Enum.ResourceType.resourceLookup.get(itemBase.getUUID()); + + return resourceType.elementOf(this.lockedResourceTypes); + } +} diff --git a/src/engine/objects/Zone.java b/src/engine/objects/Zone.java new file mode 100644 index 00000000..45314553 --- /dev/null +++ b/src/engine/objects/Zone.java @@ -0,0 +1,427 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.objects; + +import engine.Enum; +import engine.InterestManagement.HeightMap; +import engine.db.archive.DataWarehouse; +import engine.gameManager.DbManager; +import engine.gameManager.ZoneManager; +import engine.math.Bounds; +import engine.math.Vector2f; +import engine.math.Vector3fImmutable; +import engine.net.ByteBufferWriter; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class Zone extends AbstractGameObject { + + public final Set zoneBuildingSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); + public final Set zoneNPCSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); + public final Set zoneMobSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final int playerCityID; + private final String zoneName; + private final float xCoord; + private final float zCoord; + private final float yCoord; + private final int loadNum; + private final byte safeZone; + private final String Icon1; + private final String Icon2; + private final String Icon3; + public float absX = 0.0f; + public float absY = 0.0f; + public float absZ = 0.0f; + public int minLvl; + public int maxLvl; + public boolean hasBeenHotzone = false; + private ArrayList nodes = null; + private int parentZoneID; + private Zone parent = null; + private Bounds bounds; + private boolean isNPCCity = false; + private boolean isPlayerCity = false; + private String hash; + private float worldAltitude = 0; + private float seaLevel = 0; + //public static ArrayList respawnQue = new ArrayList<>(); + public static final Set respawnQue = Collections.newSetFromMap(new ConcurrentHashMap<>()); + public static long lastRespawn = 0; + /** + * ResultSet Constructor + */ + public Zone(ResultSet rs) throws SQLException { + super(rs); + this.parentZoneID = rs.getInt("parent"); + this.playerCityID = rs.getInt("isPlayerCity"); + this.isPlayerCity = this.playerCityID != 0; + this.zoneName = rs.getString("Name"); + this.xCoord = rs.getFloat("XCoord"); + this.zCoord = rs.getFloat("ZCoord"); + this.yCoord = rs.getFloat("YOffset"); + this.loadNum = rs.getInt("LoadNum"); + this.safeZone = rs.getByte("SafeZone"); + this.Icon1 = rs.getString("Icon1"); + this.Icon2 = rs.getString("Icon2"); + this.Icon3 = rs.getString("Icon3"); + this.hash = rs.getString("hash"); + + this.minLvl = rs.getInt("minLvl"); + this.maxLvl = rs.getInt("maxLvl"); + + //this needs to be here specifically for new zones created after server boot (e.g. player city zones) + Zone parentZone = ZoneManager.getZoneByUUID(parentZoneID); + + this.setParent(parentZone); + + if (this.minLvl == 0 && parentZone != null) { + this.minLvl = parentZone.minLvl; + this.maxLvl = parentZone.maxLvl; + } + + if (parentZone != null) + parentZone.addNode(this); + + // If zone doesn't yet hava a hash then write it back to the zone table + + if (hash == null) + setHash(); + + + } + + public static void serializeForClientMsg(Zone zone, ByteBufferWriter writer) { + + if (zone.loadNum == 0 && zone.playerCityID == 0) + Logger.warn("Warning! WorldServerMap with ID " + zone.getObjectUUID() + " has a loadnum of 0 (player city) and no city linked. This will probably crash the client!"); + + // Player City Terraform values serialized here. + + if (zone.playerCityID > 0) { + writer.put((byte) 1); // Player City - True + writer.putFloat(Enum.CityBoundsType.ZONE.extents); + writer.putFloat(Enum.CityBoundsType.ZONE.extents); + } else + writer.put((byte) 0); // Player City - False + + writer.putFloat(zone.xCoord); + writer.putFloat(zone.zCoord); + writer.putFloat(zone.yCoord); + + writer.putInt(0); + writer.putInt(0); + writer.putInt(zone.loadNum); + + if (zone.playerCityID > 0) { + City k = City.getCity(zone.playerCityID); + + if (k != null) { + writer.putInt(k.getObjectType().ordinal()); + writer.putInt(k.getObjectUUID()); + } else + writer.putLong(0x0); + } else { + writer.putInt(zone.getObjectType().ordinal()); + writer.putInt(zone.getObjectUUID()); + } + writer.putInt(zone.nodes.size()); + + City city = City.getCity(zone.playerCityID); + + if (city != null) + writer.putString(city.getCityName()); + else + writer.putString(zone.zoneName); + writer.put(zone.safeZone); + writer.putString(zone.Icon1); + writer.putString(zone.Icon2); + writer.putString(zone.Icon3); + writer.put((byte) 0); // Pad + + for (Zone child : zone.nodes) { + Zone.serializeForClientMsg(child, writer); + } + } + + /* Method sets a default value for player cities + * otherwise using values derived from the loadnum + * field in the obj_zone database table. + */ + public void setBounds() { + + float halfExtentX; + float halfExtentY; + + // Set initial bounds object + + this.bounds = Bounds.borrow(); + + // Player cities are assigned default value + + if (this.loadNum == 0) { + bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f); + return; + } + + Vector2f zoneSize = ZoneManager._zone_size_data.get(this.loadNum); + + // Default to player zone size on error? Maybe log this + + if (zoneSize != null) + this.bounds.setBounds(new Vector2f(this.absX, this.absZ), zoneSize, 0.0f); + else + bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f); + + } + + public int getPlayerCityUUID() { + return this.playerCityID; + } + + public String getName() { + return zoneName; + } + + public float getXCoord() { + return xCoord; + } + + public float getYCoord() { + return yCoord; + } + + public float getZCoord() { + return zCoord; + } + + public int getLoadNum() { + return loadNum; + } + + public byte getSafeZone() { + return safeZone; + } + + public String getIcon1() { + return Icon1; + } + + public void generateWorldAltitude() { + + if (ZoneManager.getSeaFloor().getObjectUUID() == this.getObjectUUID()) { + this.worldAltitude = MBServerStatics.SEA_FLOOR_ALTITUDE; + return; + } + + Zone parentZone = this.parent; + + Zone currentZone = this; + float altitude = this.absY; + + //seafloor only zone with null parent; + + while (parentZone != ZoneManager.getSeaFloor()) { + + if (parentZone.getHeightMap() != null) { + + Vector2f zoneLoc = ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone); + altitude += parentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc); + + } + currentZone = parentZone; + parentZone = parentZone.parent; + + } + + this.worldAltitude = altitude; + + if (ZoneManager.getSeaFloor().equals(this)) + this.seaLevel = 0; + else if + (this.getHeightMap() != null && this.getHeightMap().getSeaLevel() == 0) { + this.seaLevel = this.parent.seaLevel; + + } else if (this.getHeightMap() != null) { + this.seaLevel = this.worldAltitude + this.getHeightMap().getSeaLevel(); + } else { + this.seaLevel = this.parent.seaLevel; + } + + } + + public Zone getParent() { + return this.parent; + } + + public void setParent(final Zone value) { + + this.parent = value; + this.parentZoneID = (this.parent != null) ? this.parent.getObjectUUID() : 0; + + if (this.parent != null) { + this.absX = this.xCoord + parent.absX; + this.absY = this.yCoord + parent.absY; + this.absZ = this.zCoord + parent.absZ; + + if (this.minLvl == 0 || this.maxLvl == 0) { + this.minLvl = this.parent.minLvl; + this.maxLvl = this.parent.maxLvl; + } + } else { //only the Sea Floor zone does not have a parent + this.absX = this.xCoord; + this.absY = MBServerStatics.SEA_FLOOR_ALTITUDE; + this.absZ = this.zCoord; + } + + // Zone AABB is set here as it's coordinate space is world requiring a parent. + this.setBounds(); + + if (this.getHeightMap() != null && this.getHeightMap().getSeaLevel() != 0) + this.seaLevel = this.getHeightMap().getSeaLevel(); + + } + + public float getAbsX() { + return this.absX; + } + + public float getAbsY() { + return this.absY; + } + + public float getAbsZ() { + return this.absZ; + } + + public boolean isMacroZone() { + + // Macro zones have icons. + + if (this.isPlayerCity == true) + return false; + + if (this.parent == null) + return false; + + return !this.getIcon1().equals(""); + } + + public boolean isNPCCity() { + return this.isNPCCity; + } + + public void setNPCCity(boolean value) { + this.isNPCCity = value; + } + + public boolean isPlayerCity() { + return this.isPlayerCity; + } + + public void setPlayerCity(boolean value) { + this.isPlayerCity = value; + } + + public Vector3fImmutable getLoc() { + return new Vector3fImmutable(this.absX, this.absY, this.absZ); + } + + public int getParentZoneID() { + return this.parentZoneID; + } + + public ArrayList getNodes() { + if (this.nodes == null) { + this.nodes = DbManager.ZoneQueries.GET_MAP_NODES(super.getObjectUUID()); + + //Add reverse lookup for child->parent + if (this.nodes != null) + for (Zone zone : this.nodes) { + zone.setParent(this); + } + } + + return nodes; + } + + /* + * Serializing + */ + + public void addNode(Zone child) { + this.nodes.add(child); + } + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + } + + public boolean isContinent() { + + if (this.equals(ZoneManager.getSeaFloor())) + return false; + + if (this.getNodes().isEmpty()) + return false; + + if (this.getNodes().get(0).isMacroZone()) + return true; + + return this.getParent().equals(ZoneManager.getSeaFloor()); + + } + + /** + * @return the bounds + */ + public Bounds getBounds() { + return bounds; + } + + public String getHash() { + return hash; + } + + public void setHash() { + + this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID()); + + // Write hash to player character table + + DataWarehouse.writeHash(Enum.DataRecordType.ZONE, this.getObjectUUID()); + } + + // Return heightmap for this Zone. + + public HeightMap getHeightMap() { + + if (this.isPlayerCity) + return HeightMap.PlayerCityHeightMap; + + return HeightMap.heightmapByLoadNum.get(this.loadNum); + } + + public float getSeaLevel() { + return seaLevel; + } + + public float getWorldAltitude() { + return worldAltitude; + } + +} diff --git a/src/engine/pooling/ByteBufferPool.java b/src/engine/pooling/ByteBufferPool.java new file mode 100644 index 00000000..a13fbaf6 --- /dev/null +++ b/src/engine/pooling/ByteBufferPool.java @@ -0,0 +1,69 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.pooling; + +import org.pmw.tinylog.Logger; + +import java.nio.ByteBuffer; + +public class ByteBufferPool extends LinkedObjectPool { + + private final int defaultBufferSize; + + public ByteBufferPool(int defaultBufferSize) { + super(ObjectPool.DEFAULT_SIZE); + this.defaultBufferSize = defaultBufferSize; + } + + public ByteBufferPool(int size, int defaultBufferSize) { + super(size); + this.defaultBufferSize = defaultBufferSize; + } + + @Override + protected ByteBuffer makeNewObject() { + return ByteBuffer.allocate(defaultBufferSize); + } + + @Override + protected void resetObject(ByteBuffer obj) { + obj.clear(); + } + + @Override + public ByteBuffer get() { + // Logger.debug("ByteBufferPool.get() BB.capacity(): " + bb.capacity() + // + ", bb.pos(): " + bb.position() + ". Pool.size() is now: " + // + this.getPoolSize()); + return super.get(); + } + + @Override + public void put(ByteBuffer bb) { + if (bb.isDirect()) + super.put(bb); + // Logger.debug("ByteBufferPool.put() BB.capacity(): " + bb.capacity() + // + ", bb.pos(): " + bb.position() + ". Pool.size() is now: " + // + this.getPoolSize()); + } + + @Override + protected void handlePoolExhaustion() { + Logger.debug("ByteBufferPool(" + defaultBufferSize + + ") exhausted, making more objects."); + + // If none exist, make (and pool) a few + // Dont sync the loop, let the makeNewObject() + // call return before locking the pool object + for (int i = 0; i < 5; ++i) + this.makeAndAdd(); + } + +} diff --git a/src/engine/pooling/ConnectionPool.java b/src/engine/pooling/ConnectionPool.java new file mode 100644 index 00000000..0ff76ec2 --- /dev/null +++ b/src/engine/pooling/ConnectionPool.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013 MagicBane Emulator Project + * All Rights Reserved + */ +package engine.pooling; + +import engine.gameManager.ConfigManager; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class ConnectionPool extends LinkedObjectPool { + + static { + //Register the Driver + try { + Class.forName("com.mysql.cj.jdbc.Driver").newInstance(); + } catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public ConnectionPool() { + super(10); + } + + @Override + protected Connection makeNewObject() { + // Protocol + String sqlURI = "jdbc:mysql://"; + sqlURI += ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':' + ConfigManager.MB_DATABASE_PORT.getValue(); + sqlURI += '/' + ConfigManager.MB_DATABASE_NAME.getValue() + '?'; + sqlURI += "useServerPrepStmts=true"; + sqlURI += "&cachePrepStmts=false"; + sqlURI += "&cacheCallableStmts=true"; + sqlURI += "&characterEncoding=utf8"; + + Connection out = null; + try { + out = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(), + ConfigManager.MB_DATABASE_PASS.getValue()); + } catch (SQLException e) { + e.printStackTrace(); + } + + return out; + } + + @Override + protected void resetObject(Connection obj) { + + } +} diff --git a/src/engine/pooling/LinkedObjectPool.java b/src/engine/pooling/LinkedObjectPool.java new file mode 100644 index 00000000..a58b8eda --- /dev/null +++ b/src/engine/pooling/LinkedObjectPool.java @@ -0,0 +1,140 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.pooling; + + +import org.pmw.tinylog.Logger; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class LinkedObjectPool { + private final LinkedBlockingQueue pool; + private final AtomicInteger poolSize; + + /** + * Constructor that allows the caller to specify initial array size and + * percent of prefill. + * + * @param size - initial size for the containing array (pool) + */ + public LinkedObjectPool(int size) { + this.pool = new LinkedBlockingQueue<>(); + this.poolSize = new AtomicInteger(); + } + + /** + * Default Constructor that uses default initial Array size and percent + * prefill values + */ + public LinkedObjectPool() { + this(0); + } + + /** + * Forces pool to add numberOfObjects to the pool. This may cause + * internal ArrayList to resize. + * + * @param numberOfObjects + */ + public void fill(int numberOfObjects) { + for (int i = 0; i < numberOfObjects; ++i) + this.makeAndAdd(); + } + + /** + * Forces subclasses to implement factory routine for object creation. + */ + protected abstract T makeNewObject(); + + /** + * Forces subclasses to implement a way to reset object to a reusable state. + */ + protected abstract void resetObject(T obj); + + /** + * Generic Get routine. If the pool is empty, then one (or more) of T type + * objects will be created. If more than one T is created, then they will be + * put into the pool. One T will always be created and returned. + */ + public T get() { + T obj = pool.poll(); + + if (obj == null) { + //Oops pool is empty.. make a new obj + obj = this.makeNewObject(); + } else { + poolSize.decrementAndGet(); + } + + return obj; + } + + /** + * Generic put routine. If the current pool size is below threshold, this + * object will be pooled, otherwise it will be NULL'ed and scheduled for + * Garbage Collection + * + * @param obj + */ + public void put(T obj) { + //Logger.debug("Objectpool.put(). Pool size: " + pool.size()); + this.resetObject(obj); + this.poolSize.incrementAndGet(); + this.pool.add(obj); + } + + /** + * Helper method. Attempts to create and add a new T object. If this + * fails, an error is logged. + */ + protected final void makeAndAdd() { + T obj = this.makeNewObject(); + if (obj == null) { + Logger.error("Pooling failure: Object creation failed."); + } else { + this.put(obj); + } + } + + /** + * @return the current size of the pool, not the maximum capacity of the + * Array holding the pool. + */ + public final int getPoolSize() { + return this.poolSize.get(); + } + + /** + * Culls the pool and removes half of the stored objects. Removed objects + * are NULL'ed and scheduled form Garbage Collection. + * + * @return the number of Objects removed from the pool. + */ + public int cullHalf() { + int full, half; + full = this.getPoolSize(); + if (full < 1) { + return full; + } + + half = (full / 2); + + for (int i = 0; i < (full / 2); ++i) { + T obj = this.pool.poll(); + obj = null; // Null out for GC + } + return half; + } + + protected void handlePoolExhaustion() { + //Not needed in this implementation + } +} diff --git a/src/engine/pooling/MultisizeByteBufferPool.java b/src/engine/pooling/MultisizeByteBufferPool.java new file mode 100644 index 00000000..18b90336 --- /dev/null +++ b/src/engine/pooling/MultisizeByteBufferPool.java @@ -0,0 +1,184 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.pooling; + +import org.pmw.tinylog.Logger; + +import java.nio.ByteBuffer; +import java.util.HashMap; + +public class MultisizeByteBufferPool { + + /** + * List of the powers of two from 2^0 to 2^30. The index of the array + * corresponds to the power of two. Example: If you'd like to quickly lookup + * 2^19, then reference powersOfTwo[19] + */ + public static final int[] powersOfTwo = { + 1, // 2^0 + 2, // 2^1 + 4, // 2^2 + 8, // 2^3 + 16, // 2^4 + 32, // 2^5 + 64, // 2^6 + 128, // 2^7 + 256, // 2^8 + 512, // 2^9 + 1024, // 2^10 + 2048, // 2^11 + 4096, // 2^12 + 8192, // 2^13 + 16384, // 2^14 + 32768, // 2^15 + 65536, // 2^16 + 131072, // 2^17 + 262144, // 2^18 + 524288, // 2^19 + 1048576, // 2^20 + 2097152, // 2^21 + 4194304, // 2^22 + 8388608, // 2^23 + 16777216, // 2^24 + 33554432, // 2^25 + 67108864, // 2^26 + 134217728, // 2^27 + 268435456, // 2^28 + 536870912, // 2^29 + 1073741824, // 2^30 + }; + /** + * Maps a power of two (0-30) to a BB Pool + */ + private final HashMap powerToPoolMap = new HashMap<>(); + + public MultisizeByteBufferPool() { + super(); + } + + /** + * Returns the next power of two that is larger than or equal too the input + * value + * + * @param value + * @return the power of two that is larger than or equal too the input + * value. A return of -1 indicates out of range. + */ + public static int getPowerThatWillFit(final int value) { + return (value == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(value - 1)); + } + + private static ByteBufferPool makeByteBufferPool(int bbInitialSize) { + return new ByteBufferPool(bbInitialSize); + } + + /** + * Gets a ByteBuffer that is of the size 2^powerOfTwo from the + * appropriate pool. + * + * @param powerOfTwo int range of 0-30 + * @return + */ + public ByteBuffer getBuffer(int powerOfTwo) { + // Validate input + if (powerOfTwo > 30 || powerOfTwo < 0) { + Logger.error("powerOfTwo out of range (0-30) in getBuffer(). Got: " + powerOfTwo); + return null; + } + + // Check to see if there is a pool for this size + ByteBufferPool bbp = this.getByteBufferPool(powerOfTwo); + return bbp.get(); + } + + /** + * Internal getter to provide synchronization. Adds ByteBufferPool if not mapped. + * + * @param powerOfTwo + * @return + */ + private ByteBufferPool getByteBufferPool(Integer powerOfTwo) { + synchronized (this.powerToPoolMap) { + // Check to see if there is a pool for this size + ByteBufferPool bbp = powerToPoolMap.get(powerOfTwo); + + if (bbp == null) { + bbp = MultisizeByteBufferPool.makeByteBufferPool(powersOfTwo[powerOfTwo]); + this.putByteBufferPool(powerOfTwo, bbp); + } + return bbp; + } + } + + /** + * Internal setter to provide synchronization + * + * @param powerOfTwo + * @param bbp + * @return + */ + private ByteBufferPool putByteBufferPool(Integer powerOfTwo, + ByteBufferPool bbp) { + synchronized (this.powerToPoolMap) { + return powerToPoolMap.put(powerOfTwo, bbp); + } + } + + public ByteBuffer getBufferToFit(int numOfBytes) { + int pow = MultisizeByteBufferPool.getPowerThatWillFit(numOfBytes); + return this.getBuffer(pow); + } + + /** + * Puts a ByteBuffer that is of the size 2^powerOfTwo back into the + * appropriate pool. + * + * @param bb - Bytebuffer to put into a pool + */ + public void putBuffer(ByteBuffer bb) { + + if (bb == null) + return; + + // determine size: + int pow = MultisizeByteBufferPool.getPowerThatWillFit(bb.capacity()); + + // if we get here and pow == -1 then we have a bytebuffer > 2^30 !!!! + // so just file it under power of 30; + if (pow == -1) { + pow = 30; + } + + // get pool + ByteBufferPool bbp = this.getByteBufferPool(pow); + + // put buffer (back) into pool + bbp.put(bb); + } + + /** + * Returns the size of the ByteBufferPool mapped to the given powerOfTwo. + * + * @param powerOfTwo int range of 0-30 + * @return size of pool mapped to provided powerOfTwo. Returns -1 on + * error and lastError will be set. + */ + public int getSizeOfPool(int powerOfTwo) { + if (powerOfTwo > 30 || powerOfTwo < 0) { + Logger.error("powerOfTwo out of range (0-30) in getSizeOfPool(). Got: " + + powerOfTwo); + return -1; + } + ByteBufferPool bbp = this.getByteBufferPool(powerOfTwo); + + return bbp.getPoolSize(); + } + +} diff --git a/src/engine/pooling/ObjectPool.java b/src/engine/pooling/ObjectPool.java new file mode 100644 index 00000000..3ebfeb9e --- /dev/null +++ b/src/engine/pooling/ObjectPool.java @@ -0,0 +1,192 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.pooling; + +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +public abstract class ObjectPool { + protected final static int DEFAULT_SIZE = 1000; + + // Simple + quick list + private final ArrayList pool; + + /** + * Once the ArrayList fills to threshold, subsequent .put() calls + * result in the object being thrown away. Default is 75% of supplied + * size. + */ + private int threshold; + + /** + * Constructor that allows the caller to specify initial array size and + * percent of prefill. + * + * @param size - initial size for the containing array (pool) + */ + public ObjectPool(int size) { + if (size == 0) { + size = DEFAULT_SIZE; + } + + threshold = (int) (0.75 * size); + + this.pool = new ArrayList<>(size); + + } + + /** + * Default Constructor that uses default initial Array size and percent + * prefill values + */ + public ObjectPool() { + this(DEFAULT_SIZE); + } + + /** + * Forces pool to add numberOfObjects to the pool. This may cause + * internal ArrayList to resize. + * + * @param numberOfObjects + */ + public void fill(int numberOfObjects) { + for (int i = 0; i < numberOfObjects; ++i) + this.makeAndAdd(); + } + + /** + * Forces subclasses to implement factory routine for object creation. + */ + protected abstract T makeNewObject(); + + /** + * Forces subclasses to implement a way to reset object to a reusable state. + */ + protected abstract void resetObject(T obj); + + /** + * Generic Get routine. If the pool is empty, then one (or more) of T type + * objects will be created. If more than one T is created, then they will be + * put into the pool. One T will always be created and returned. + */ + public T get() { + synchronized (pool) { + //Logger.debug("Objectpool.get(). Pool size before get: " + pool.size()); + if (pool.size() > 0) { + return pool.remove(0); + } + } + + this.handlePoolExhaustion(); + + T obj = this.makeNewObject(); + + if (obj == null) { + Logger.error("Pooling failure: Object creation failed."); + } + return obj; + } + + protected void handlePoolExhaustion() { + Logger.debug("Pool exhausted, making more objects."); + + // If none exist, make (and pool) a few + // Dont sync the loop, let the makeNewObject() + // call return before locking the pool object + for (int i = 0; i < 5; ++i) + this.makeAndAdd(); + + } + + /** + * Generic put routine. If the current pool size is below threshold, this + * object will be pooled, otherwise it will be NULL'ed and scheduled for + * Garbage Collection + * + * @param obj + */ + public void put(T obj) { + synchronized (pool) { + if (pool.size() >= this.threshold) { + //Logger.debug("Objectpool.put() rejected. Pool size: " + pool.size()); + return; + } + //Logger.debug("Objectpool.put(). Pool size: " + pool.size()); + this.resetObject(obj); + this.pool.add(obj); + } + } + + /** + * Helper method. Attempts to create and add a new T object. If this + * fails, an error is logged. + */ + protected final void makeAndAdd() { + T obj = this.makeNewObject(); + if (obj == null) { + Logger.error("Pooling failure: Object creation failed."); + } else { + this.put(obj); + } + } + + /** + * @return the current size of the pool, not the maximum capacity of the + * Array holding the pool. + */ + public final int getPoolSize() { + synchronized (this.pool) { + return this.pool.size(); + } + } + + /** + * Culls the pool and removes half of the stored objects. Removed objects + * are NULL'ed and scheduled form Garbage Collection. + * + * @return the number of Objects removed from the pool. + */ + public int cullHalf() { + int full, half; + synchronized (this.pool) { + full = this.pool.size(); + if (full < 1) { + return full; + } + + half = (full / 2); + + for (int i = 0; i < (full / 2); ++i) { + T obj = this.get(); + obj = null; // Null out for GC + } + } + return half; + } + + /** + * @return the threshold + */ + public final int getThreshold() { + return threshold; + } + + /** + * @param threshold the threshold to set + */ + public void setThreshold(int threshold) { + if (threshold < 0) + return; + + this.threshold = threshold; + } + +} diff --git a/src/engine/pooling/Poolable.java b/src/engine/pooling/Poolable.java new file mode 100644 index 00000000..d1be89cb --- /dev/null +++ b/src/engine/pooling/Poolable.java @@ -0,0 +1,14 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.pooling; + +public interface Poolable { + public void clear(); +} diff --git a/src/engine/powers/ActionsBase.java b/src/engine/powers/ActionsBase.java new file mode 100644 index 00000000..5264d18b --- /dev/null +++ b/src/engine/powers/ActionsBase.java @@ -0,0 +1,269 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers; + +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.Enum.StackType; +import engine.gameManager.PowersManager; +import engine.objects.*; +import engine.powers.poweractions.AbstractPowerAction; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class ActionsBase { + + public int UUID; + public String IDString; + public String effectID; + public int minTrains; + public int maxTrains; + public float duration; + public float ramp; + public boolean addFormula; + public String stackType; + public StackType stackTypeType; + public int stackOrder; + + public boolean greaterThanEqual = false; + public boolean always = false; + public boolean greaterThan = false; + public String stackPriority; + + private AbstractPowerAction powerAction; + + /** + * No Table ID Constructor + */ + public ActionsBase() { + + } + + /** + * ResultSet Constructor + */ + public ActionsBase(ResultSet rs, HashMap apa) throws SQLException { + + this.UUID = rs.getInt("ID"); + this.IDString = rs.getString("powerID"); + this.effectID = rs.getString("effectID"); + this.minTrains = rs.getInt("minTrains"); + this.maxTrains = rs.getInt("maxTrains"); + this.duration = rs.getFloat("duration"); + this.ramp = rs.getFloat("ramp"); + this.addFormula = (rs.getInt("useAddFormula") == 1) ? true : false; + this.stackType = rs.getString("stackType"); + this.stackTypeType = StackType.GetStackType(this.stackType); + this.stackOrder = rs.getInt("stackOrder"); + this.stackPriority = rs.getString("stackPriority"); + + switch (stackPriority) { + case "GreaterThanOrEqualTo": + this.greaterThanEqual = true; + break; + case "Always": + this.always = true; + break; + case "GreaterThan": + this.greaterThan = true; + break; + } + this.powerAction = apa.get(this.effectID); + } + + protected ActionsBase(int uUID, String effectID, int minTrains, int maxTrains, float duration, float ramp, + boolean addFormula, String stackType, int stackOrder, boolean greaterThanEqual, boolean always, + boolean greaterThan, AbstractPowerAction powerAction) { + super(); + UUID = uUID; + this.effectID = effectID; + this.minTrains = minTrains; + this.maxTrains = maxTrains; + this.duration = duration; + this.ramp = ramp; + this.addFormula = addFormula; + this.stackType = stackType; + this.stackTypeType = StackType.GetStackType(this.stackType); + if (this.stackTypeType == null) + Logger.info("Invalid Stack Type " + this.stackTypeType + " for " + this.effectID); + this.stackOrder = stackOrder; + this.greaterThanEqual = greaterThanEqual; + this.always = always; + this.greaterThan = greaterThan; + this.powerAction = powerAction; + + if (this.greaterThanEqual) + this.stackPriority = "GreaterThanOrEqualTo"; + else if (this.always) + this.stackPriority = "Always"; + else if (this.greaterThan) + this.stackPriority = "GreaterThan"; + + } + + // public static ArrayList getActionsBase(String ID) { + // PreparedStatementShared ps = null; + // ArrayList out = new ArrayList(); + // try { + // ps = new PreparedStatementShared("SELECT * FROM actions where powerID = ?"); + // ps.setString(1, ID); + // ResultSet rs = ps.executeQuery(); + // while (rs.next()) { + // ActionsBase toAdd = new ActionsBase(rs); + // out.add(toAdd); + // } + // rs.close(); + // } catch (Exception e) { + // Logger.error("ActionsBase", e); + // } finally { + // ps.release(); + // } + // return out; + // } + + public static void getActionsBase(HashMap powers, HashMap apa) { + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM static_power_action"); + ResultSet rs = ps.executeQuery(); + String IDString; + ActionsBase toAdd; + PowersBase pb; + while (rs.next()) { + IDString = rs.getString("powerID"); + pb = powers.get(IDString); + if (pb != null) { + toAdd = new ActionsBase(rs, apa); + pb.getActions().add(toAdd); + } + } + rs.close(); + } catch (Exception e) { + Logger.error(e.toString()); + } finally { + ps.release(); + } + + int gateID = 5000; + for (String IDString : Runegate.GetAllOpenGateIDStrings()) { + gateID++; + ActionsBase openGateActionBase = new ActionsBase(gateID, "OPENGATE", 5, 9999, 0, 0, true, "IgnoreStack", 0, true, false, false, PowersManager.getPowerActionByIDString("OPENGATE")); + + PowersBase openGatePower = powers.get(IDString); + + if (openGatePower == null) { + Logger.error("no powerbase for action " + IDString); + break; + } + openGatePower.getActions().add(openGateActionBase); + } + } + + + public int getUUID() { + return this.UUID; + } + + public String getEffectID() { + return this.effectID; + } + + public int getMinTrains() { + return this.minTrains; + } + + public int getMaxTrains() { + return this.maxTrains; + } + + public float getDuration() { + return this.duration; + } + + public AbstractPowerAction getPowerAction() { + return this.powerAction; + } + + public int getDuration(int trains) { + if (this.addFormula) + return (int) ((this.duration + (this.ramp * trains)) * 1000); + else + return (int) ((this.duration * (1 + (this.ramp * trains))) * 1000); + } + + public float getDurationAsFloat(int trains) { + if (this.addFormula) + return ((this.duration + (this.ramp * trains)) * 1000); + else + return ((this.duration * (1 + (this.ramp * trains))) * 1000); + } + + public int getDurationInSeconds(int trains) { + if (this.addFormula) + return (int) (this.duration + (this.ramp * trains)); + else + return (int) (this.duration * (1 + (this.ramp * trains))); + } + + public String getStackType() { + return this.stackType; + } + + public int getStackOrder() { + return this.stackOrder; + } + + public boolean greaterThanEqual() { + return this.greaterThanEqual; + } + + public boolean greaterThan() { + return this.greaterThan; + } + + public boolean always() { + return this.always; + } + + //Add blocked types here + public boolean blocked(AbstractWorldObject awo, PowersBase pb, int trains) { + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + PlayerBonuses bonus = ac.getBonuses(); + if (bonus == null) + return false; + + //TODO make this more efficient then testing strings + if (this.stackType.equals("Stun") && bonus.getBool(ModType.ImmuneTo, SourceType.Stun)) + return true; //Currently stun immune. Skip stun + else if (this.stackType.equals("Snare") && bonus.getBool(ModType.ImmuneTo, SourceType.Snare)) + return true; //Currently snare immune. Skip snare + else if (this.stackType.equals("Blindness") && bonus.getBool(ModType.ImmuneTo, SourceType.Blind)) + return true; //Currently blind immune. Skip blind + else if (this.stackType.equals("PowerInhibitor") && bonus.getBool(ModType.ImmuneTo, SourceType.Powerblock)) + return true; //Currently power block immune. Skip power block + else if (this.stackType.equals("Root") && bonus.getBool(ModType.ImmuneTo, SourceType.Root)) + return true; + // else if (pb.isHeal() && (bonus.getByte("immuneTo.Heal")) >= trains) + // return true; //Currently shadowmantled. Skip heals + else if (this.stackType.equals("Flight") && bonus.getBool(ModType.NoMod, SourceType.Fly)) + return true; + else if (this.stackType.equals("Track") && bonus.getBool(ModType.CannotTrack, SourceType.None)) + return true; + else + return pb.vampDrain() && bonus.getBool(ModType.BlockedPowerType, SourceType.VAMPDRAIN); + } + return false; + } +} diff --git a/src/engine/powers/DamageShield.java b/src/engine/powers/DamageShield.java new file mode 100644 index 00000000..3b5663c5 --- /dev/null +++ b/src/engine/powers/DamageShield.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers; + +import engine.Enum.DamageType; + +public class DamageShield { + + private final DamageType damageType; + private final float amount; + private final boolean usePercent; + + public DamageShield(DamageType damageType, float amount, boolean usePercent) { + super(); + this.damageType = damageType; + this.amount = amount; + this.usePercent = usePercent; + } + + public DamageType getDamageType() { + return this.damageType; + } + + public float getAmount() { + return this.amount; + } + + public boolean usePercent() { + return this.usePercent; + } + + @Override + public String toString() { + return "ds.DamageType: " + this.damageType.name() + ", Amount: " + this.amount + ", UsePercent: " + this.usePercent; + } +} diff --git a/src/engine/powers/EffectsBase.java b/src/engine/powers/EffectsBase.java new file mode 100644 index 00000000..a1e7b9e6 --- /dev/null +++ b/src/engine/powers/EffectsBase.java @@ -0,0 +1,779 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers; + +import engine.Enum; +import engine.Enum.DamageType; +import engine.Enum.EffectSourceType; +import engine.Enum.GameObjectType; +import engine.Enum.PowerFailCondition; +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.job.JobContainer; +import engine.jobs.AbstractEffectJob; +import engine.jobs.DamageOverTimeJob; +import engine.jobs.FinishSpireEffectJob; +import engine.jobs.NoTimeJob; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ApplyEffectMsg; +import engine.objects.*; +import engine.powers.effectmodifiers.AbstractEffectModifier; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +public class EffectsBase { + + public static HashMap> effectSourceTypeMap = new HashMap<>(); + public static HashMap> modifiersMap = new HashMap<>(); + public static HashMap>> OldEffectsMap = new HashMap<>(); + public static HashMap>> NewEffectsMap = new HashMap<>(); + public static HashMap>> ChangedEffectsMap = new HashMap<>(); + public static HashMap> EffectFailConditions = new HashMap<>(); + public static HashMap> EffectDamageTypes = new HashMap<>(); + public static HashSet DefaultModifiers = new HashSet<>(); + private static ConcurrentHashMap itemEffectsByName = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private static int NewID = 3000; + private int UUID; + private String IDString; + // private String name; + private int token; + private float amount; + private float amountRamp; + // flags + private boolean isItemEffect; + private boolean isSpireEffect; + private boolean ignoreMod; + private boolean dontSave; + private boolean cancelOnAttack = false; + private boolean cancelOnAttackSwing = false; + private boolean cancelOnCast = false; + private boolean cancelOnCastSpell = false; + private boolean cancelOnEquipChange = false; + private boolean cancelOnLogout = false; + private boolean cancelOnMove = false; + private boolean cancelOnNewCharm = false; + private boolean cancelOnSit = false; + private boolean cancelOnTakeDamage = false; + private boolean cancelOnTerritoryClaim = false; + private boolean cancelOnUnEquip = false; + private boolean useRampAdd; + private boolean isPrefix = false; //used by items + private boolean isSuffix = false; //used by items + private String name = ""; + private float value = 0; + private ConcurrentHashMap resourceCosts = new ConcurrentHashMap<>(); + private ConcurrentHashMap sourceTypes = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + + /** + * No Table ID Constructor + */ + public EffectsBase() { + + } + + public EffectsBase(EffectsBase copyEffect, int newToken, String IDString) { + + UUID = NewID++; + this.IDString = IDString; + this.token = newToken; + + //filll + if (copyEffect == null) { + int flags = 0; + this.isItemEffect = ((flags & 1) != 0) ? true : false; + this.isSpireEffect = ((flags & 2) != 0) ? true : false; + this.ignoreMod = ((flags & 4) != 0) ? true : false; + this.dontSave = ((flags & 8) != 0) ? true : false; + + if (this.IDString.startsWith("PRE-")) + this.isPrefix = true; + else if (this.IDString.startsWith("SUF-")) + this.isSuffix = true; + + } + + + this.amount = copyEffect.amount; + this.amountRamp = copyEffect.amountRamp; + this.isItemEffect = copyEffect.isItemEffect; + this.isSpireEffect = copyEffect.isSpireEffect; + this.ignoreMod = copyEffect.ignoreMod; + this.dontSave = copyEffect.dontSave; + this.cancelOnAttack = copyEffect.cancelOnAttack; + this.cancelOnAttackSwing = copyEffect.cancelOnAttackSwing; + this.cancelOnCast = copyEffect.cancelOnCast; + this.cancelOnCastSpell = copyEffect.cancelOnCastSpell; + this.cancelOnEquipChange = copyEffect.cancelOnEquipChange; + this.cancelOnLogout = copyEffect.cancelOnLogout; + this.cancelOnMove = copyEffect.cancelOnMove; + this.cancelOnNewCharm = copyEffect.cancelOnNewCharm; + this.cancelOnSit = copyEffect.cancelOnSit; + this.cancelOnTakeDamage = copyEffect.cancelOnTakeDamage; + this.cancelOnTerritoryClaim = copyEffect.cancelOnTerritoryClaim; + this.cancelOnUnEquip = copyEffect.cancelOnUnEquip; + this.useRampAdd = copyEffect.useRampAdd; + this.isPrefix = copyEffect.isPrefix; + this.isSuffix = copyEffect.isSuffix; + this.name = copyEffect.name; + this.value = copyEffect.value; + this.resourceCosts = copyEffect.resourceCosts; + + } + + /** + * ResultSet Constructor + */ + public EffectsBase(ResultSet rs) throws SQLException { + + this.UUID = rs.getInt("ID"); + this.IDString = rs.getString("IDString"); + this.name = rs.getString("name"); + this.token = rs.getInt("Token"); + + //override tokens for some effects like Safemode that use the Action Token instead of the effect Token, + switch (this.IDString) { + case "INVIS-D": + this.token = -1661751254; + break; + case "SafeMode": + this.token = -1661750486; + break; + + } + int flags = rs.getInt("flags"); + this.isItemEffect = ((flags & 1) != 0) ? true : false; + this.isSpireEffect = ((flags & 2) != 0) ? true : false; + this.ignoreMod = ((flags & 4) != 0) ? true : false; + this.dontSave = ((flags & 8) != 0) ? true : false; + + if (this.IDString.startsWith("PRE-")) + this.isPrefix = true; + else if (this.IDString.startsWith("SUF-")) + this.isSuffix = true; + // getFailConditions(); + } + + public static void getFailConditions(HashMap effects) { + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM static_power_failcondition WHERE powerOrEffect = 'Effect';"); + + ResultSet rs = ps.executeQuery(); + PowerFailCondition failCondition = null; + + Object value; + while (rs.next()) { + String fail = rs.getString("type"); + + + String IDString = rs.getString("IDString"); + int token = DbManager.hasher.SBStringHash(IDString); + failCondition = PowerFailCondition.valueOf(fail); + if (failCondition == null) { + Logger.error("Couldn't Find FailCondition " + fail + " for " + IDString); + continue; + } + + if (EffectsBase.EffectFailConditions.get(IDString) == null) { + EffectsBase.EffectFailConditions.put(IDString, new HashSet<>()); + } + + EffectsBase.EffectFailConditions.get(IDString).add(failCondition); + EffectsBase eb = effects.get(IDString); + + switch (failCondition) { + + case TakeDamage: + + + // dont go any further. + if (eb == null) { + break; + } + + eb.cancelOnTakeDamage = true; + + + eb.amount = rs.getFloat("amount"); + eb.amountRamp = rs.getFloat("ramp"); + eb.useRampAdd = rs.getBoolean("UseAddFormula"); + + String damageType1 = rs.getString("damageType1"); + String damageType2 = rs.getString("damageType2"); + String damageType3 = rs.getString("damageType3"); + + + if (damageType1.isEmpty() && damageType2.isEmpty() && damageType3.isEmpty()) + break; + + if (!EffectsBase.EffectDamageTypes.containsKey(eb.getToken())) { + EffectsBase.EffectDamageTypes.put(eb.getToken(), new HashSet<>()); + } + if (damageType1.equalsIgnoreCase("Crushing")) + damageType1 = "Crush"; + if (damageType1.equalsIgnoreCase("Piercing")) + damageType1 = "Pierce"; + if (damageType1.equalsIgnoreCase("Slashing")) + damageType1 = "Slash"; + + if (damageType2.equalsIgnoreCase("Crushing")) + damageType2 = "Crush"; + if (damageType2.equalsIgnoreCase("Piercing")) + damageType2 = "Pierce"; + if (damageType2.equalsIgnoreCase("Slashing")) + damageType2 = "Slash"; + + if (damageType3.equalsIgnoreCase("Crushing")) + damageType3 = "Crush"; + if (damageType3.equalsIgnoreCase("Piercing")) + damageType3 = "Pierce"; + if (damageType3.equalsIgnoreCase("Slashing")) + damageType3 = "Slash"; + DamageType dt = getDamageType(damageType1); + if (dt != null) + EffectsBase.EffectDamageTypes.get(eb.token).add(dt); + + dt = getDamageType(damageType2); + if (dt != null) + EffectsBase.EffectDamageTypes.get(eb.token).add(dt); + dt = getDamageType(damageType3); + if (dt != null) + EffectsBase.EffectDamageTypes.get(eb.token).add(dt); + break; + case Attack: + eb.cancelOnAttack = true; + break; + case AttackSwing: + eb.cancelOnAttackSwing = true; + break; + case Cast: + eb.cancelOnCast = true; + break; + case CastSpell: + eb.cancelOnCastSpell = true; + break; + case EquipChange: + eb.cancelOnEquipChange = true; + break; + case Logout: + eb.cancelOnLogout = true; + break; + case Move: + eb.cancelOnMove = true; + break; + case NewCharm: + eb.cancelOnNewCharm = true; + break; + case Sit: + eb.cancelOnSit = true; + break; + case TerritoryClaim: + eb.cancelOnTerritoryClaim = true; + break; + case UnEquip: + eb.cancelOnUnEquip = true; + break; + } + } + + rs.close(); + } catch (Exception e) { + Logger.error(e); + } finally { + ps.release(); + } + + } + + private static DamageType getDamageType(String name) { + try { + switch (name) { + case "Crushing": + name = "Crush"; + break; + case "Slashing": + name = "Slash"; + break; + case "Piercing": + name = "Pierce"; + break; + } + if (name.isEmpty()) + return null; + else + return DamageType.valueOf(name); + } catch (Exception e) { + Logger.error(name); + return null; + } + } + + public static String getItemEffectsByName(String string) { + if (EffectsBase.itemEffectsByName.containsKey(string)) + return EffectsBase.itemEffectsByName.get(string); + return ""; + } + + public static void addItemEffectsByName(String name, String ID) { + EffectsBase.itemEffectsByName.put(name, ID); + } + + public float getDamageAmount(int trains) { + if (useRampAdd) + return (amount + (amountRamp * trains)); + else + return (amount * (1 + (amountRamp * trains))); + } + + // public String getName() { + // return this.name; + // } + + public boolean damageTypeSpecific() { + + return EffectsBase.EffectDamageTypes.containsKey(this.token); + + } + + public boolean containsDamageType(DamageType dt) { + if (!EffectsBase.EffectDamageTypes.containsKey(this.token)) + return false; + return EffectsBase.EffectDamageTypes.get(this.token).contains(dt); + } + + public int getUUID() { + return this.UUID; + } + + public String getIDString() { + return this.IDString; + } + + public int getToken() { + return this.token; + } + + //For Debugging purposes. + public void setToken(int token) { + this.token = token; + } + + public ConcurrentHashMap getSourceTypes() { + return this.sourceTypes; + } + + public HashSet getModifiers() { + + if (EffectsBase.modifiersMap.containsKey(this.IDString) == false) + return EffectsBase.DefaultModifiers; + + return EffectsBase.modifiersMap.get(this.IDString); + } + + public boolean isItemEffect() { + return this.isItemEffect; + } + + public boolean isSpireEffect() { + return this.isSpireEffect; + } + + public boolean ignoreMod() { + return this.ignoreMod; + } + + public boolean dontSave() { + return this.dontSave; + } + + public boolean isPrefix() { + return this.isPrefix; + } + + public boolean isSuffix() { + return this.isSuffix; + } + + public void startEffect(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + + // Add SourceTypes for dispel + + if (this.token != 0) { + if (effect == null) { + Logger.error("AbstractEffectModifier.applyEffectModifier: missing FinishEffectTimeJob"); + return; + } + // AbstractWorldObject source = effect.getSource(); + if (source == null) { + Logger.error("AbstractEffectModifier.applyEffectModifier: missing source"); + return; + } + PowersBase pb = effect.getPower(); + if (pb == null) { + Logger.error("AbstractEffectModifier.applyEffectModifier: missing power"); + return; + } + ActionsBase ab = effect.getAction(); + if (ab == null) { + Logger.error("AbstractEffectModifier.applyEffectModifier: missing action"); + return; + } + + //don't send effect if dead, except for death shroud + if (!awo.isAlive()) { + if (pb.getToken() != 1672601862) + return; + } + + + if (!effect.skipSendEffect()) { + // float duration = (pb.isChant()) ? pb.getChantDuration() * 1000 : ab.getDuration(trains); + float duration = ab.getDurationInSeconds(trains); + if (pb.getToken() == 1672601862) { + + Effect eff = awo.getEffects().get("DeathShroud"); + + + if (eff != null) { + JobContainer jc = eff.getJobContainer(); + + + if (jc != null) { + duration = jc.timeOfExection() - System.currentTimeMillis(); + duration *= .001f; + } + } + } + + + if (duration > 0f) { + int removeToken = this.token; + ApplyEffectMsg pum = new ApplyEffectMsg(); + if (effect.getAction() != null) + if (effect.getAction().getPowerAction() != null + && PowersManager.ActionTokenByIDString.containsKey(effect.getAction().getPowerAction().getIDString())) + try { + removeToken = PowersManager.ActionTokenByIDString.get(effect.getAction().getPowerAction().getIDString()); + } catch (Exception e) { + removeToken = this.token; + } + + pum.setEffectID(removeToken); + pum.setSourceType(source.getObjectType().ordinal()); + pum.setSourceID(source.getObjectUUID()); + pum.setTargetType(awo.getObjectType().ordinal()); + pum.setTargetID(awo.getObjectUUID()); + pum.setNumTrains(trains); + pum.setDuration((int) duration); + // pum.setDuration((pb.isChant()) ? (int)pb.getChantDuration() : ab.getDurationInSeconds(trains)); + pum.setPowerUsedID(pb.getToken()); + pum.setPowerUsedName(pb.getName()); + DispatchMessage.sendToAllInRange(awo, pum); + } + + if (awo.getObjectType().equals(GameObjectType.Item)) { + if (source.getCharItemManager() != null) { + source.getCharItemManager().updateInventory(); + } + } + } + + // call modifiers to do their job + if (!effect.skipApplyEffect()) { + for (AbstractEffectModifier em : this.getModifiers()) + em.applyEffectModifier(source, awo, trains, effect); + } + } + } + + // Send end effect message to client + public void endEffect(AbstractWorldObject source, AbstractWorldObject awo, int trains, PowersBase pb, AbstractEffectJob effect) { + if (awo == null) { + Logger.error("endEffect(): Null AWO object passed in."); + return; + } + if (pb == null) { + Logger.error("endEffect(): Null PowerBase object passed in."); + return; + } + if (!effect.skipCancelEffect() && !effect.isNoOverwrite()) { + + int sendToken = this.token; + + if (effect.getAction() != null) + if (effect.getAction().getPowerAction() != null + && PowersManager.ActionTokenByIDString.containsKey(effect.getAction().getPowerAction().getIDString())) + try { + sendToken = PowersManager.ActionTokenByIDString.get(effect.getAction().getPowerAction().getIDString()); + } catch (Exception e) { + sendToken = this.token; + } + ApplyEffectMsg pum = new ApplyEffectMsg(); + pum.setEffectID(sendToken); + if (source != null) { + pum.setSourceType(source.getObjectType().ordinal()); + pum.setSourceID(source.getObjectUUID()); + } else { + pum.setSourceType(0); + pum.setSourceID(0); + } + pum.setTargetType(awo.getObjectType().ordinal()); + pum.setTargetID(awo.getObjectUUID()); + pum.setUnknown02(2); + pum.setNumTrains(0); + pum.setDuration(-1); + pum.setPowerUsedID(pb.getToken()); + pum.setPowerUsedName(pb.getName()); + DispatchMessage.sendToAllInRange(awo, pum); + + } + } + + public void endEffectNoPower(int trains, AbstractEffectJob effect) { + + AbstractWorldObject source = effect.getSource(); + + if (source == null) + return; + + if (!effect.skipCancelEffect() && !effect.isNoOverwrite()) { + ApplyEffectMsg pum = new ApplyEffectMsg(); + pum.setEffectID(this.token); + + pum.setSourceType(source.getObjectType().ordinal()); + pum.setSourceID(source.getObjectUUID()); + pum.setTargetType(source.getObjectType().ordinal()); + pum.setTargetID(source.getObjectUUID()); + pum.setUnknown02(2); + pum.setNumTrains(0); + pum.setDuration(-1); + pum.setUnknown06((byte) 1); + pum.setEffectSourceType(effect.getEffectSourceType()); + pum.setEffectSourceID(effect.getEffectSourceID()); + pum.setPowerUsedID(0); + pum.setPowerUsedName(this.name); + + if (source.getObjectType() == GameObjectType.PlayerCharacter) { + Dispatch dispatch = Dispatch.borrow((PlayerCharacter) source, pum); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + } + } + } + + public void sendEffect(AbstractEffectJob effect, int duration, ClientConnection conn) { + if (effect == null && conn != null) + return; + + if (conn == null) + return; + AbstractWorldObject source = effect.getSource(); + AbstractWorldObject awo = effect.getTarget(); + int trains = effect.getTrains(); + if (source == null || awo == null) + return; + + if (this.token != 0) { + PowersBase pb = effect.getPower(); + if (pb == null) { + Logger.error("AbstractEffectModifier.applyEffectModifier: missing power"); + return; + } + ActionsBase ab = effect.getAction(); + if (ab == null) { + Logger.error("AbstractEffectModifier.applyEffectModifier: missing action"); + return; + } + + //don't send effect if dead, except for death shroud + if (!awo.isAlive()) { + if (pb.getToken() != 1672601862) + return; + } + + //duration for damage over times is (total time - (number of ticks x 5 seconds per tick)) + if (effect instanceof DamageOverTimeJob) + duration = ((DamageOverTimeJob) effect).getTickLength(); + + // float dur = (pb.isChant()) ? pb.getChantDuration() * 1000 : ab.getDuration(trains); + float dur = ab.getDuration(trains); + if (dur > 0f) { + ApplyEffectMsg pum = new ApplyEffectMsg(); + pum.setEffectID(this.token); + pum.setSourceType(source.getObjectType().ordinal()); + pum.setSourceID(source.getObjectUUID()); + pum.setTargetType(awo.getObjectType().ordinal()); + pum.setTargetID(awo.getObjectUUID()); + pum.setNumTrains(trains); + pum.setDuration(duration); + pum.setPowerUsedID(pb.getToken()); + pum.setPowerUsedName(pb.getName()); + + Dispatch dispatch = Dispatch.borrow(conn.getPlayerCharacter(), pum); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + } + } + } + + public void sendEffectNoPower(AbstractEffectJob effect, int duration, ClientConnection conn) { + + if (effect == null && conn != null) + return; + + if (conn == null) + return; + + AbstractWorldObject source = effect.getSource(); + AbstractWorldObject awo = effect.getTarget(); + int trains = effect.getTrains(); + + if (source == null || awo == null) + return; + + if (this.token != 0) { + //don't send effect if dead, except for death shroud + if (!awo.isAlive()) { + return; + } + + //duration for damage over times is (total time - (number of ticks x 5 seconds per tick)) + if (effect instanceof DamageOverTimeJob) + duration = ((DamageOverTimeJob) effect).getTickLength(); + else if (effect instanceof FinishSpireEffectJob) + duration = 45; + else if (effect instanceof NoTimeJob) + duration = -1; + + // float dur = (pb.isChant()) ? pb.getChantDuration() * 1000 : ab.getDuration(trains); + + ApplyEffectMsg pum = new ApplyEffectMsg(); + pum.setEffectID(this.token); + pum.setSourceType(source.getObjectType().ordinal()); + pum.setSourceID(source.getObjectUUID()); + pum.setTargetType(source.getObjectType().ordinal()); + pum.setTargetID(source.getObjectUUID()); + pum.setUnknown06((byte) 1); + pum.setEffectSourceType(effect.getEffectSourceType()); + pum.setEffectSourceID(effect.getEffectSourceID()); + pum.setNumTrains(trains); + pum.setDuration(duration); + pum.setPowerUsedID(0); + pum.setPowerUsedName(this.name); + + Dispatch dispatch = Dispatch.borrow(conn.getPlayerCharacter(), pum); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); + + } + } + + public boolean containsSource(EffectSourceType sourceType) { + if (EffectsBase.effectSourceTypeMap.containsKey(this.token) == false) + return false; + return EffectsBase.effectSourceTypeMap.get(this.token).contains(sourceType); + + } + + public boolean cancelOnAttack() { + return this.cancelOnAttack; + } + + public boolean cancelOnAttackSwing() { + return this.cancelOnAttackSwing; + } + + public boolean cancelOnCast() { + return this.cancelOnCast; + } + + public boolean cancelOnCastSpell() { + return this.cancelOnCastSpell; + } + + public boolean cancelOnEquipChange() { + return this.cancelOnEquipChange; + } + + public boolean cancelOnLogout() { + return this.cancelOnLogout; + } + + public boolean cancelOnMove() { + return this.cancelOnMove; + } + + public boolean cancelOnNewCharm() { + return this.cancelOnNewCharm; + } + + public boolean cancelOnSit() { + return this.cancelOnSit; + } + + public boolean cancelOnTakeDamage() { + return this.cancelOnTakeDamage; + } + + public boolean cancelOnTerritoryClaim() { + return this.cancelOnTerritoryClaim; + } + + public boolean cancelOnUnEquip() { + return this.cancelOnUnEquip; + } + + public String getDamageTypes() { + String text = ""; + if (!EffectsBase.EffectDamageTypes.containsKey(this.token)) + return text; + for (DamageType type : EffectsBase.EffectDamageTypes.get(this.token)) { + text += type.name() + ' '; + } + return text; + } + + public String getName() { + + return name; + } + + public void setName(String name) { + this.name = name; + } + + public float getValue() { + return value; + } + + public void setValue(float Value) { + this.value = Value; + } + + public ConcurrentHashMap getResourcesForEffect() { + if (this.resourceCosts.isEmpty()) { + ArrayList effectsCostList = DbManager.EffectsResourceCostsQueries.GET_ALL_EFFECT_RESOURCES(this.IDString); + for (EffectsResourceCosts erc : effectsCostList) { + this.resourceCosts.put(ItemBase.getItemBase(erc.getResourceID()), erc.getAmount()); + } + } + return this.resourceCosts; + } + + +} diff --git a/src/engine/powers/FailCondition.java b/src/engine/powers/FailCondition.java new file mode 100644 index 00000000..faeece1e --- /dev/null +++ b/src/engine/powers/FailCondition.java @@ -0,0 +1,118 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers; + +import engine.objects.PreparedStatementShared; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + + +public class FailCondition { + + private String IDString; + private Boolean forPower; + private String type; + private float amount; + private float ramp; + private boolean rampAdd; + + // private String damageType1; + // private String damageType2; + // private String damageType3; + + /** + * No Table ID Constructor + */ + public FailCondition() { + + } + + /** + * ResultSet Constructor + */ + public FailCondition(ResultSet rs) throws SQLException { + + this.IDString = rs.getString("IDString"); + this.forPower = (rs.getString("powerOrEffect").equals("Power")) ? true : false; + this.type = rs.getString("type"); + this.amount = rs.getFloat("amount"); + this.ramp = rs.getFloat("ramp"); + this.rampAdd = (rs.getInt("useAddFormula") == 1) ? true : false; + // this.damageType1 = rs.getString("damageType1"); + // this.damageType2 = rs.getString("damageType2"); + // this.damageType3 = rs.getString("damageType3"); + } + + public static ArrayList getAllFailConditions() { + PreparedStatementShared ps = null; + ArrayList out = new ArrayList<>(); + try { + ps = new PreparedStatementShared("SELECT * FROM failconditions"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + FailCondition toAdd = new FailCondition(rs); + out.add(toAdd); + } + rs.close(); + } catch (Exception e) { + Logger.error(e); + + } finally { + ps.release(); + } + return out; + } + + public String getIDString() { + return this.IDString; + } + + public String getType() { + return this.type; + } + + public boolean forPower() { + return this.forPower; + } + + public float getAmount() { + return this.amount; + } + + public float getRamp() { + return this.ramp; + } + + public float getAmountForTrains(float trains) { + if (this.rampAdd) + return this.amount + (this.ramp * trains); + else + return this.amount * (1 + (this.ramp * trains)); + } + + public boolean useRampAdd() { + return this.rampAdd; + } + + // public String getDamageType1() { + // return this.damageType1; + // } + + // public String getDamageType2() { + // return this.damageType2; + // } + + // public String getDamageType3() { + // return this.damageType3; + // } +} diff --git a/src/engine/powers/MobPowerEntry.java b/src/engine/powers/MobPowerEntry.java new file mode 100644 index 00000000..1aee9848 --- /dev/null +++ b/src/engine/powers/MobPowerEntry.java @@ -0,0 +1,17 @@ +package engine.powers; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MobPowerEntry { + + public int token; + public int rank; + + + public MobPowerEntry(ResultSet rs) throws SQLException { + this.token = rs.getInt("token"); + this.rank = rs.getInt("rank"); + } + +} diff --git a/src/engine/powers/PowerPrereq.java b/src/engine/powers/PowerPrereq.java new file mode 100644 index 00000000..086185a5 --- /dev/null +++ b/src/engine/powers/PowerPrereq.java @@ -0,0 +1,108 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers; + +import engine.objects.PreparedStatementShared; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class PowerPrereq { + + private String effect; + private String message; + private boolean mainHand; + private boolean required; + + /** + * No Table ID Constructor + */ + public PowerPrereq() { + + } + + /** + * ResultSet Constructor + */ + public PowerPrereq(ResultSet rs, int type) throws SQLException { + + // this.IDString = rs.getString("IDString"); + if (type == 1) { + this.effect = rs.getString("messageone"); + this.message = rs.getString("messagetwo"); + this.mainHand = false; + this.required = false; + } else if (type == 2) { + String sl = rs.getString("messageone"); + if (sl.equals("RHELD")) + this.mainHand = true; + else if (sl.equals("LHELD")) + this.mainHand = false; + this.effect = ""; + this.message = rs.getString("messagetwo"); + this.required = (rs.getInt("required") == 1) ? true : false; + } else { //targetEffectPrereq + this.effect = rs.getString("messageone"); + this.message = ""; + this.mainHand = false; + this.required = (rs.getInt("required") == 1) ? true : false; + } + } + + public static void getAllPowerPrereqs(HashMap powers) { + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM static_power_powercastprereq"); + ResultSet rs = ps.executeQuery(); + int type; + String IDString; + PowerPrereq toAdd; + PowersBase pb; + while (rs.next()) { + IDString = rs.getString("IDString"); + pb = powers.get(IDString); + if (pb != null) { + type = rs.getInt("Type"); + toAdd = new PowerPrereq(rs, type); + if (type == 1) + pb.getEffectPrereqs().add(toAdd); + else if (type == 2) + pb.getEquipPrereqs().add(toAdd); + else + pb.getTargetEffectPrereqs().add(toAdd); + } + } + rs.close(); + } catch (Exception e) { + Logger.error(e.toString()); + } finally { + ps.release(); + } + } + + public String getEffect() { + return this.effect; + } + + public String getMessage() { + return this.message; + } + + public boolean mainHand() { + return this.mainHand; + } + + public boolean isRequired() { + return this.required; + } +} diff --git a/src/engine/powers/PowersBase.java b/src/engine/powers/PowersBase.java new file mode 100644 index 00000000..9cfc8c01 --- /dev/null +++ b/src/engine/powers/PowersBase.java @@ -0,0 +1,636 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers; + +import engine.Enum.PowerCategoryType; +import engine.Enum.PowerTargetType; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +public class PowersBase { + + public final ArrayList monsterTypeRestrictions = new ArrayList<>(); + public final ArrayList actions = new ArrayList<>(); + public final ArrayList effectPrereqs = new ArrayList<>(); + public final ArrayList targetEffectPrereqs = new ArrayList<>(); + public final ArrayList equipPrereqs = new ArrayList<>(); + public int UUID; + public String name; + public int token; + public String IDString; + public String category; + public int skillID; + public float range; + public float cost; + public float costRamp; + public float castTime; + public float castTimeRamp; + public float cooldown; + public float recycleTime; + public float recycleRamp; + public int maxTrains; + public float hateValue; + public float hateRamp; + public String monsterTypePrereq; // target limited to these types + public String skillName; + public float weaponRange = 15f; + // aoe related + public boolean isAOE = true; + public boolean useCone = false; + public boolean usePointBlank = false; + public boolean useSphere = false; + public float radius; + public byte groupReq; // who the spell won't hit + public int maxNumMobTargets; + public int maxNumPlayerTargets; + // chant related + public float chantDuration; + public int chantIterations; + // valid target types from targetType field + public boolean targetPlayer = false; + public boolean targetMob = false; + public boolean targetPet = false; + public boolean targetNecroPet = false; + public boolean targetSelf = false; + public boolean targetWeapon = false; + public boolean targetCorpse = false; + public boolean targetBuilding = false; + public boolean targetGroup = false; + public boolean targetGuildLeader = false; + public boolean targetJewelry = false; + public boolean targetArmor = false; + public boolean targetItem = false; + // flags + public boolean isCasterFriendly = false; // from groupReq + public boolean isGroupFriendly = false; // from groupReq + public boolean isGroupOnly = false; // from groupReq + public boolean mustHitPets = false; // from groupReq + public boolean isNationFriendly = false; // from groupReq + public boolean targetFromLastTarget = false; // from unknown06 + public boolean targetFromSelf = false; // from unknown06 + public boolean targetFromName = false; // from unknown06 + public boolean targetFromNearbyMobs = false; // from unknown06 + public boolean useHealth = false; // from costType + public boolean useMana = false; // from costType + public boolean useStamina = false; // from costType + public boolean isSpell = true; // from skillOrSpell field + public boolean allowedInCombat = false; // from combat field + public boolean allowedOutOfCombat = false; // from combat field + public boolean regularPlayerCanCast = false; // from grantOverrideVar + public boolean hateRampAdd = true; // 1 bit flag + public boolean costRampAdd = true; // 2 bit flag + public boolean recycleRampAdd = true; // 4 bit flag + public boolean initRampAdd = true; // 8 bit flag + public boolean canCastWhileMoving = false; // 16 bit flag + public boolean canCastWhileFlying = false; // 32 bit flag + public boolean isChant = false; // 64 bit flag + public boolean losCheck = false; // 128 bit flag + public boolean sticky = false; // 256 bit flag + public boolean isAdminPower = false; // 512 bit flag + public boolean requiresHitRoll = false; // 1024 bit flag + public boolean isWeaponPower = false; // from category + public boolean isHeal = false; //from category + public boolean isTrack = false; //from category + public boolean isHarmful = true; + public boolean vampDrain = false; + public boolean cancelOnCastSpell = false; + public boolean cancelOnTakeDamage = false; + public PowerTargetType targetType; + public PowerCategoryType powerCategory; + public String description; + + /** + * No Table ID Constructor + */ + public PowersBase() { + + } + + /** + * ResultSet Constructor + */ + public PowersBase(ResultSet rs) throws SQLException { + + this.UUID = rs.getInt("ID"); + this.name = rs.getString("name").trim(); + this.token = rs.getInt("token"); + this.skillName = rs.getString("skillName").trim(); + this.IDString = rs.getString("IDString").trim(); + this.isSpell = (rs.getString("skillOrSpell").equals("SPELL")) ? true : false; + this.skillID = rs.getInt("skillID"); + this.range = rs.getFloat("range"); + this.description = (rs.getString("description")).trim().replace("\r\n ", ""); + String ct = rs.getString("costType").trim(); + if (ct.equals("HEALTH")) + this.useHealth = true; + else if (ct.equals("MANA")) + this.useMana = true; + else if (ct.equals("STAMINA")) + this.useStamina = true; + ct = rs.getString("targetType").trim(); + if (ct.equals("BUILDING")) + this.targetBuilding = true; + else if (ct.equals("CORPSE")) + this.targetCorpse = true; + else if (ct.equals("GROUP")) + this.targetGroup = true; + else if (ct.equals("GUILDLEADER")) + this.targetGuildLeader = true; + else if (ct.equals("MOBILE")) { + this.targetMob = true; + this.targetPet = true; // sure on this one? + } else if (ct.equals("PC")) + this.targetPlayer = true; + else if (ct.equals("SELF")) + this.targetSelf = true; + else if (ct.equals("PET")) + this.targetPet = true; + else if (ct.equals("NECROPET")) + this.targetNecroPet = true; + else if (ct.equals("ARMOR")) + this.targetArmor = true; + else if (ct.equals("WEAPON")) + this.targetWeapon = true; + else if (ct.equals("JEWELRY")) + this.targetJewelry = true; + else if (ct.equals("ITEM")) { + this.targetItem = true; + this.targetJewelry = true; + this.targetArmor = true; + this.targetWeapon = true; + } else if (ct.equals("ARMORWEAPONJEWELRY")) { + this.targetArmor = true; + this.targetWeapon = true; + this.targetJewelry = true; + } else if (ct.equals("PCMOBILE")) { + this.targetPlayer = true; + this.targetMob = true; + this.targetPet = true; // sure on this one? + } else if (ct.equals("WEAPONARMOR")) { + this.targetWeapon = true; + this.targetArmor = true; + } else { + Logger.info("Missed " + ct + " targetType"); + } + String cat = rs.getString("category").trim(); + this.category = cat; + + + if (cat.isEmpty()) + this.powerCategory = PowerCategoryType.NONE; + else + this.powerCategory = PowerCategoryType.valueOf(cat.replace("-", "")); + + + if (cat.equals("WEAPON")) { + this.isWeaponPower = true; + this.isHarmful = false; + if (this.skillName.equals("Bow") || this.skillName.equals("Crossbow") || this.skillName.equals("Archery")) + this.weaponRange = 1000f; + else if (this.skillName.equals("Throwing")) + this.weaponRange = 60f; + else + this.weaponRange = 15f; + } else if (cat.equals("HEAL") || cat.equals("GROUPHEAL")) { + this.isHeal = true; + this.isHarmful = false; + } else if (cat.equals("TRACK")) { + this.isTrack = true; + this.isHarmful = false; + } else if (cat.equals("AE") || cat.equals("AEDAMAGE") || + cat.equals("BREAKFLY") || + cat.equals("DAMAGE") || cat.equals("DEBUFF") || + cat.equals("MOVE") || cat.equals("SPECIAL") || + cat.equals("SPIREDISABLE")) + this.isHarmful = true; + else if (cat.equals("CHANT")) { + this.isHarmful = ct.equals("MOBILE") || ct.equals("PC") || ct.equals("PCMOBILE"); + } else if (cat.equals("DISPEL")) { + //TODO this needs broken down better later + this.isHarmful = false; + } else if (cat.isEmpty()) { + if (ct.equals("MOBILE") || ct.equals("PCMOBILE")) + this.isHarmful = true; + else if (ct.equals("PC")) { + this.isHarmful = this.token != 429607195 && this.token != 429425915; + } else + this.isHarmful = false; + } else + this.isHarmful = false; + + if (cat.equals("VAMPDRAIN")) { + this.vampDrain = true; + this.isHarmful = true; + } + + this.cost = rs.getFloat("cost"); + this.costRamp = rs.getFloat("costRamp"); + this.castTime = rs.getFloat("castTime"); + this.castTimeRamp = rs.getFloat("initRamp"); + this.cooldown = rs.getFloat("cooldown"); + this.recycleTime = rs.getFloat("recycleTime"); + this.recycleRamp = rs.getFloat("recycleRamp"); + this.maxTrains = rs.getInt("maxTrains"); + this.hateValue = rs.getFloat("hateValue"); + this.hateRamp = rs.getFloat("hateRamp"); + ct = rs.getString("unknown06").trim(); + if (this.targetSelf) { + } else if (ct.equals("CLICK")) + if (!this.targetGroup) + this.targetFromLastTarget = true; + else if (ct.equals("NAME")) + this.targetFromName = true; + else if (ct.equals("NEARBYMOBS")) + this.targetFromNearbyMobs = true; + this.monsterTypePrereq = rs.getString("monsterTypePrereqs").trim(); + ct = rs.getString("radiusType").trim(); + if (ct.equals("CONE")) + this.useCone = true; + else if (ct.equals("POINTBLANK")) + this.usePointBlank = true; + else if (ct.equals("SPHERE")) + this.useSphere = true; + else + this.isAOE = false; + this.radius = rs.getFloat("radius"); + ct = rs.getString("groupReq").trim(); + if (ct.equals("CASTER")) + this.isCasterFriendly = true; + else if (ct.equals("GROUP")) { + this.isGroupFriendly = true; + this.isCasterFriendly = true; + } else if (ct.equals("ALLBUTGROUP")) + this.isGroupOnly = true; + else if (ct.equals("ALLBUTPETS")) + this.mustHitPets = true; + else if (ct.equals("NATION")) { + this.isNationFriendly = true; + this.isCasterFriendly = true; + } + this.maxNumMobTargets = rs.getInt("maxNumMobTargets"); + this.maxNumPlayerTargets = rs.getInt("maxNumPlayerTargets"); + this.chantDuration = rs.getFloat("chantDuration"); + this.chantIterations = rs.getInt("chantIterations"); + ct = rs.getString("combat").trim(); + if (ct.equals("COMBAT")) + this.allowedInCombat = true; + else if (ct.equals("NONCOMBAT")) + this.allowedOutOfCombat = true; + else if (ct.equals("BOTH")) { + this.allowedInCombat = true; + this.allowedOutOfCombat = true; + } + ct = rs.getString("grantOverideVar").trim(); + if (ct.equals("PGOV_PLAYER")) + this.regularPlayerCanCast = true; + int flags = rs.getInt("flags"); + if ((flags & 1) == 0) + this.hateRampAdd = false; + if ((flags & 2) == 0) + this.costRampAdd = false; + if ((flags & 4) == 0) + this.recycleRampAdd = false; + if ((flags & 8) == 0) + this.initRampAdd = false; + if ((flags & 16) != 0) + this.canCastWhileMoving = true; + if ((flags & 32) != 0) + this.canCastWhileFlying = true; + if ((flags & 64) != 0) + this.isChant = true; + if ((flags & 128) != 0) + this.losCheck = true; + if ((flags & 256) != 0) + this.sticky = true; + if ((flags & 512) != 0) + this.isAdminPower = true; + if ((flags & 1024) != 0) + this.requiresHitRoll = true; + ct = rs.getString("monsterTypeRestrict1").trim(); + if (!ct.isEmpty()) + this.monsterTypeRestrictions.add(ct); + ct = rs.getString("monsterTypeRestrict2").trim(); + if (!ct.isEmpty()) + this.monsterTypeRestrictions.add(ct); + ct = rs.getString("monsterTypeRestrict3").trim(); + if (!ct.isEmpty()) + this.monsterTypeRestrictions.add(ct); + } + + + public String getName() { + return this.name; + } + + public int getMaxTrains() { + return this.maxTrains; + } + + public int getUUID() { + return this.UUID; + } + + public String getIDString() { + return this.IDString; + } + + public int getToken() { + if (this.IDString.equals("BLEED-DOT-10.5-RANGE")) + return -369682965; + return this.token; + } + + public int getCastTime(int trains) { // returns cast time in ms + if (this.initRampAdd) + return (int) ((this.castTime + (this.castTimeRamp * trains)) * 1000); + else + return (int) ((this.castTime * (1 + (this.castTimeRamp * trains))) * 1000); + } + + public int getRecycleTime(int trains) { // returns cast time in ms + if (this.recycleRampAdd) + return (int) (((this.recycleTime + (this.recycleRamp * trains)) * 1000) + getCastTime(trains)); + else + return (int) (((this.recycleTime * (1 + (this.recycleRamp * trains))) * 1000) + getCastTime(trains)); + } + + // public ArrayList getConditions() { + // return this.conditions; + // } + + public ArrayList getEffectPrereqs() { + return this.effectPrereqs; + } + + public ArrayList getTargetEffectPrereqs() { + return this.targetEffectPrereqs; + } + + public ArrayList getEquipPrereqs() { + return this.equipPrereqs; + } + + public ArrayList getActions() { + return this.actions; + } + + public boolean usePointBlank() { + return this.usePointBlank; + } + + public float getRadius() { + return this.radius; + } + + public int getMaxNumMobTargets() { + return this.maxNumMobTargets; + } + + public int getMaxNumPlayerTargets() { + return this.maxNumPlayerTargets; + } + + public boolean cancelOnCastSpell() { + return this.cancelOnCastSpell; + } + + public boolean cancelOnTakeDamage() { + return this.cancelOnTakeDamage; + } + + public boolean allowedInCombat() { + return this.allowedInCombat; + } + + public boolean allowedOutOfCombat() { + return this.allowedOutOfCombat; + } + + public boolean isCasterFriendly() { + return this.isCasterFriendly; + } + + public boolean isGroupFriendly() { + return this.isGroupFriendly; + } + + public boolean isNationFriendly() { + return this.isNationFriendly; + } + + public boolean isGroupOnly() { + return this.isGroupOnly; + } + + public boolean mustHitPets() { + return this.mustHitPets; + } + + public boolean targetFromLastTarget() { + return this.targetFromLastTarget; + } + + public boolean targetFromSelf() { + return this.targetFromSelf; + } + + public boolean targetFromName() { + return this.targetFromName; + } + + public boolean targetFromNearbyMobs() { + return this.targetFromNearbyMobs; + } + + public float getRange() { + return this.range; + } + + public boolean requiresHitRoll() { + return this.requiresHitRoll; + } + + public boolean regularPlayerCanCast() { + return this.regularPlayerCanCast; + } + + public boolean isSpell() { + return this.isSpell; + } + + public boolean isHarmful() { + return this.isHarmful; + } + + public boolean targetPlayer() { + return this.targetPlayer; + } + + public boolean targetMob() { + return this.targetMob; + } + + public boolean targetPet() { + return this.targetPet; + } + + public boolean targetNecroPet() { + return this.targetNecroPet; + } + + public boolean targetSelf() { + return this.targetSelf; + } + + public boolean targetCorpse() { + return this.targetCorpse; + } + + public boolean targetBuilding() { + return this.targetBuilding; + } + + public boolean targetGroup() { + return this.targetGroup; + } + + public boolean targetGuildLeader() { + return this.targetGuildLeader; + } + + public boolean targetJewelry() { + return this.targetJewelry; + } + + public boolean targetArmor() { + return this.targetArmor; + } + + public boolean targetWeapon() { + return this.targetWeapon; + } + + public boolean targetItem() { + return this.targetItem; + } + + public long getCooldown() { + return (long) (this.cooldown * 1000); // return + // in ms + } + + public boolean useHealth() { + return this.useHealth; + } + + public boolean useMana() { + return this.useMana; + } + + public boolean useStamina() { + return this.useStamina; + } + + public float getCost(int trains) { + if (this.costRampAdd) + return this.cost + (this.costRamp * trains); + else + return this.cost * (1 + (this.costRamp * trains)); + + } + + public float getHateValue() { + return this.hateValue; + } + + public float getHateRamp() { + return this.hateRamp; + } + + public float getHateValue(int trains) { + return this.hateValue + (this.hateRamp * trains); + } + + public boolean canCastWhileMoving() { + return this.canCastWhileMoving; + } + + public boolean canCastWhileFlying() { + return this.canCastWhileFlying; + } + + public boolean isAOE() { + return isAOE; + } + + public boolean isChant() { + return isChant; + } + + public int getChantIterations() { + return chantIterations; + } + + public float getChantDuration() { + return chantDuration; + } + + public boolean isWeaponPower() { + return isWeaponPower; + } + + public boolean isHeal() { + return isHeal; + } + + public boolean isTrack() { + return isTrack; + } + + public boolean vampDrain() { + return vampDrain; + } + + public void setCancelOnCastSpell(boolean value) { + this.cancelOnCastSpell = value; + } + + public void setCancelOnTakeDamage(boolean value) { + this.cancelOnTakeDamage = value; + } + + public String getSkillName() { + return this.skillName; + } + + public String getMonsterTypePrereq() { + return this.monsterTypePrereq; + } + + public String getCategory() { + return this.category; + } + + public float getWeaponRange() { + return this.weaponRange; + } + + public PowerCategoryType getPowerCategoryType() { + return this.powerCategory; + } + + public String getDescription() { + return description; + } + +} diff --git a/src/engine/powers/RangeBasedAwo.java b/src/engine/powers/RangeBasedAwo.java new file mode 100644 index 00000000..66a499c8 --- /dev/null +++ b/src/engine/powers/RangeBasedAwo.java @@ -0,0 +1,104 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers; + +import engine.Enum; +import engine.Enum.GameObjectType; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerCharacter; + +import java.util.HashSet; + + +//This package creates a list of AbstractWorldObjects +//sorted by range from a specified point. +public class RangeBasedAwo implements Comparable { + + private float range; + private AbstractWorldObject awo; + + public RangeBasedAwo(Float range, AbstractWorldObject awo) { + super(); + this.range = range; + this.awo = awo; + } + + public static HashSet createList(HashSet awolist, Vector3fImmutable searchLoc) { + HashSet rbal = new HashSet<>(); + for (AbstractWorldObject awo : awolist) { + RangeBasedAwo rba = new RangeBasedAwo(searchLoc.distance(awo.getLoc()), awo); + rbal.add(rba); + } + return rbal; + } + + public static HashSet getSortedList(HashSet awolist, Vector3fImmutable searchLoc, int maxPlayers, int maxMobs) { + int playerCnt = 0; + int mobCnt = 0; + int maxCnt = (maxPlayers > maxMobs) ? maxPlayers : maxMobs; + HashSet rbal = RangeBasedAwo.createList(awolist, searchLoc); + awolist = new HashSet<>(); + for (RangeBasedAwo rba : rbal) { + if (awolist.size() >= maxCnt) + return awolist; + AbstractWorldObject awo = rba.awo; + + if (awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + if (playerCnt < maxPlayers) { + awolist.add(awo); + playerCnt++; + } + } else if (awo.getObjectType().equals(Enum.GameObjectType.Mob)) { + if (mobCnt < maxMobs) { + awolist.add(awo); + mobCnt++; + } + } + } + return awolist; + } + + public static HashSet getTrackList(HashSet awolist, PlayerCharacter pc, int max) { + Vector3fImmutable searchLoc = pc.getLoc(); + int cnt = 0; + HashSet rbal = RangeBasedAwo.createList(awolist, searchLoc); + HashSet aclist = new HashSet<>(); + for (RangeBasedAwo rba : rbal) { + if (aclist.size() >= max) + return aclist; + AbstractWorldObject awo = rba.awo; + + if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) + if (((PlayerCharacter) awo).isCSR()) + continue; + + if (AbstractWorldObject.IsAbstractCharacter(awo) && !(pc.equals(awo))) { + aclist.add((AbstractCharacter) awo); + cnt++; + } + } + return aclist; + } + + @Override + public int compareTo(RangeBasedAwo obj) throws ClassCastException { + return (int) (this.range - obj.range); + } + + public float getRange() { + return this.range; + } + + public AbstractWorldObject getAwo() { + return this.awo; + } +} diff --git a/src/engine/powers/effectmodifiers/AbstractEffectModifier.java b/src/engine/powers/effectmodifiers/AbstractEffectModifier.java new file mode 100644 index 00000000..b35eda3f --- /dev/null +++ b/src/engine/powers/effectmodifiers/AbstractEffectModifier.java @@ -0,0 +1,121 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; +import engine.powers.EffectsBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public abstract class AbstractEffectModifier { + + public float minMod; + public SourceType sourceType; + public ModType modType; + protected EffectsBase parent; + protected int UUID; + protected String IDString; + protected String effectType; + protected float maxMod; + protected float percentMod; + protected float ramp; + protected boolean useRampAdd; + protected String type; + protected String string1; + protected String string2; + + public AbstractEffectModifier(ResultSet rs) throws SQLException { + + this.UUID = rs.getInt("ID"); + this.IDString = rs.getString("IDString"); + this.effectType = rs.getString("modType"); + this.modType = ModType.GetModType(this.effectType); + this.type = rs.getString("type").replace("\"", ""); + this.sourceType = SourceType.GetSourceType(this.type.replace(" ", "").replace("-", "")); + this.minMod = rs.getFloat("minMod"); + this.maxMod = rs.getFloat("maxMod"); + this.percentMod = rs.getFloat("percentMod"); + this.ramp = rs.getFloat("ramp"); + this.useRampAdd = (rs.getInt("useRampAdd") == 1) ? true : false; + + this.string1 = rs.getString("string1"); + this.string2 = rs.getString("string2"); + } + + + public int getUUID() { + return this.UUID; + } + + // public String getIDString() { + // return this.IDString; + // } + + public String getmodType() { + return this.effectType; + } + + public float getMinMod() { + return this.minMod; + } + + public float getMaxMod() { + return this.maxMod; + } + + public float getPercentMod() { + return this.percentMod; + } + + public float getRamp() { + return this.ramp; + } + + public String getType() { + return this.type; + } + + public String getString1() { + return this.string1; + } + + public String getString2() { + return this.string2; + } + + public EffectsBase getParent() { + return this.parent; + } + + public void setParent(EffectsBase value) { + this.parent = value; + } + + public void applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + _applyEffectModifier(source, awo, trains, effect); + } + + protected abstract void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect); + + public abstract void applyBonus(AbstractCharacter ac, int trains); + + public abstract void applyBonus(Item item, int trains); + + public abstract void applyBonus(Building building, int trains); +} diff --git a/src/engine/powers/effectmodifiers/AdjustAboveDmgCapEffectModifier.java b/src/engine/powers/effectmodifiers/AdjustAboveDmgCapEffectModifier.java new file mode 100644 index 00000000..20672b83 --- /dev/null +++ b/src/engine/powers/effectmodifiers/AdjustAboveDmgCapEffectModifier.java @@ -0,0 +1,52 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class AdjustAboveDmgCapEffectModifier extends AbstractEffectModifier { + + public AdjustAboveDmgCapEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + PlayerBonuses bonus = ac.getBonuses(); + bonus.setFloat(this, amount); + + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/AmbidexterityEffectModifier.java b/src/engine/powers/effectmodifiers/AmbidexterityEffectModifier.java new file mode 100644 index 00000000..fab03894 --- /dev/null +++ b/src/engine/powers/effectmodifiers/AmbidexterityEffectModifier.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class AmbidexterityEffectModifier extends AbstractEffectModifier { + + public AmbidexterityEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(ModType.Ambidexterity, SourceType.None, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ArmorPiercingEffectModifier.java b/src/engine/powers/effectmodifiers/ArmorPiercingEffectModifier.java new file mode 100644 index 00000000..aab12746 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ArmorPiercingEffectModifier.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class ArmorPiercingEffectModifier extends AbstractEffectModifier { + + public ArmorPiercingEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + amount = this.percentMod; + bonus.addFloat(this, amount * 0.01f); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/AttackDelayEffectModifier.java b/src/engine/powers/effectmodifiers/AttackDelayEffectModifier.java new file mode 100644 index 00000000..59af7200 --- /dev/null +++ b/src/engine/powers/effectmodifiers/AttackDelayEffectModifier.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class AttackDelayEffectModifier extends AbstractEffectModifier { + + public AttackDelayEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + PlayerBonuses bonus = ac.getBonuses(); + bonus.addFloat(this, amount); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/AttributeEffectModifier.java b/src/engine/powers/effectmodifiers/AttributeEffectModifier.java new file mode 100644 index 00000000..831d8a62 --- /dev/null +++ b/src/engine/powers/effectmodifiers/AttributeEffectModifier.java @@ -0,0 +1,59 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class AttributeEffectModifier extends AbstractEffectModifier { + + public AttributeEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + ac.update(); + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/BlackMantleEffectModifier.java b/src/engine/powers/effectmodifiers/BlackMantleEffectModifier.java new file mode 100644 index 00000000..5619e98f --- /dev/null +++ b/src/engine/powers/effectmodifiers/BlackMantleEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.jobs.AbstractEffectJob; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class BlackMantleEffectModifier extends AbstractEffectModifier { + + public BlackMantleEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + SourceType sourceType = SourceType.valueOf(this.type); + + if (sourceType == null) { + Logger.error("Bad Source Type for " + this.type); + return; + } + + if (this.type.equals("Heal")) + bonus.setFloat(this, trains); + else + bonus.setBool(ModType.ImmuneTo, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/BladeTrailsEffectModifier.java b/src/engine/powers/effectmodifiers/BladeTrailsEffectModifier.java new file mode 100644 index 00000000..5fd08bbc --- /dev/null +++ b/src/engine/powers/effectmodifiers/BladeTrailsEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class BladeTrailsEffectModifier extends AbstractEffectModifier { + + public BladeTrailsEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/BlockEffectModifier.java b/src/engine/powers/effectmodifiers/BlockEffectModifier.java new file mode 100644 index 00000000..feb9386c --- /dev/null +++ b/src/engine/powers/effectmodifiers/BlockEffectModifier.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class BlockEffectModifier extends AbstractEffectModifier { + + public BlockEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + PlayerBonuses bonus = ac.getBonuses(); + bonus.setFloat(this, amount); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/BlockedPowerTypeEffectModifier.java b/src/engine/powers/effectmodifiers/BlockedPowerTypeEffectModifier.java new file mode 100644 index 00000000..f55e903d --- /dev/null +++ b/src/engine/powers/effectmodifiers/BlockedPowerTypeEffectModifier.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.ModType; +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; + + +public class BlockedPowerTypeEffectModifier extends AbstractEffectModifier { + + public BlockedPowerTypeEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + + + for (String effect : ac.getEffects().keySet()) { + Effect eff = ac.getEffects().get(effect); + ModType toBlock = ModType.None; + + switch (this.sourceType) { + case Invisible: + toBlock = ModType.Invisible; + break; + } + + HashSet aemList = eff.getEffectModifiers(); + for (AbstractEffectModifier aem : aemList) { + if (aem.modType.equals(toBlock)) { + ac.endEffect(effect); + } + } + + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/CannotAttackEffectModifier.java b/src/engine/powers/effectmodifiers/CannotAttackEffectModifier.java new file mode 100644 index 00000000..67f72aaf --- /dev/null +++ b/src/engine/powers/effectmodifiers/CannotAttackEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class CannotAttackEffectModifier extends AbstractEffectModifier { + + public CannotAttackEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/CannotCastEffectModifier.java b/src/engine/powers/effectmodifiers/CannotCastEffectModifier.java new file mode 100644 index 00000000..1048b9e1 --- /dev/null +++ b/src/engine/powers/effectmodifiers/CannotCastEffectModifier.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum; +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class CannotCastEffectModifier extends AbstractEffectModifier { + + public CannotCastEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + if (ac.getObjectType().equals(Enum.GameObjectType.Mob)) { + Mob mob = (Mob) ac; + } + + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/CannotMoveEffectModifier.java b/src/engine/powers/effectmodifiers/CannotMoveEffectModifier.java new file mode 100644 index 00000000..5cf0b8e7 --- /dev/null +++ b/src/engine/powers/effectmodifiers/CannotMoveEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class CannotMoveEffectModifier extends AbstractEffectModifier { + + public CannotMoveEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + ac.stopMovement(ac.getMovementLoc()); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/CannotTrackEffectModifier.java b/src/engine/powers/effectmodifiers/CannotTrackEffectModifier.java new file mode 100644 index 00000000..e5c235a9 --- /dev/null +++ b/src/engine/powers/effectmodifiers/CannotTrackEffectModifier.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class CannotTrackEffectModifier extends AbstractEffectModifier { + + public CannotTrackEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/CharmedEffectModifier.java b/src/engine/powers/effectmodifiers/CharmedEffectModifier.java new file mode 100644 index 00000000..0d464e94 --- /dev/null +++ b/src/engine/powers/effectmodifiers/CharmedEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class CharmedEffectModifier extends AbstractEffectModifier { + + public CharmedEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ConstrainedAmbidexterityEffectModifier.java b/src/engine/powers/effectmodifiers/ConstrainedAmbidexterityEffectModifier.java new file mode 100644 index 00000000..47e8047b --- /dev/null +++ b/src/engine/powers/effectmodifiers/ConstrainedAmbidexterityEffectModifier.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class ConstrainedAmbidexterityEffectModifier extends AbstractEffectModifier { + + public ConstrainedAmbidexterityEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setString(this, this.type); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/DCVEffectModifier.java b/src/engine/powers/effectmodifiers/DCVEffectModifier.java new file mode 100644 index 00000000..16f0283c --- /dev/null +++ b/src/engine/powers/effectmodifiers/DCVEffectModifier.java @@ -0,0 +1,57 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class DCVEffectModifier extends AbstractEffectModifier { + + public DCVEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = (amount) / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/DREffectModifier.java b/src/engine/powers/effectmodifiers/DREffectModifier.java new file mode 100644 index 00000000..5ff9096f --- /dev/null +++ b/src/engine/powers/effectmodifiers/DREffectModifier.java @@ -0,0 +1,63 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class DREffectModifier extends AbstractEffectModifier { + + public DREffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + //Defense Rating (defense bonus for armor) + } + + @Override + public void applyBonus(Item item, int trains) { + if (item == null) + return; + String key; + float amount = 0f; + if (this.percentMod != 0f) { + if (this.useRampAdd) + amount = (this.percentMod + (this.ramp * trains)) / 100f; + else + amount = (this.percentMod * (1 + (this.ramp * trains))) / 100f; + amount = amount / 100; + key = "DR.percent"; + } else { + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + key = "DR"; + } + item.addBonus(this, amount); + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/DamageCapEffectModifier.java b/src/engine/powers/effectmodifiers/DamageCapEffectModifier.java new file mode 100644 index 00000000..403dc7dc --- /dev/null +++ b/src/engine/powers/effectmodifiers/DamageCapEffectModifier.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class DamageCapEffectModifier extends AbstractEffectModifier { + + public DamageCapEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + PlayerBonuses bonus = ac.getBonuses(); + bonus.setFloat(this, amount); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/DamageShieldEffectModifier.java b/src/engine/powers/effectmodifiers/DamageShieldEffectModifier.java new file mode 100644 index 00000000..d6b0e66b --- /dev/null +++ b/src/engine/powers/effectmodifiers/DamageShieldEffectModifier.java @@ -0,0 +1,68 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.DamageType; +import engine.jobs.AbstractEffectJob; +import engine.objects.*; +import engine.powers.DamageShield; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class DamageShieldEffectModifier extends AbstractEffectModifier { + + public DamageShieldEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + float amount; + boolean usePercent; + if (this.percentMod != 0) { + amount = this.percentMod; + usePercent = true; + } else { + amount = this.minMod; + usePercent = false; + } + + if (this.ramp > 0f) { + float mod = this.ramp * trains; + if (this.useRampAdd) + amount += mod; + else + amount *= (1 + mod); + } + + DamageType dt = DamageType.valueOf(this.type); + if (dt != null) { + DamageShield ds = new DamageShield(dt, amount, usePercent); + PlayerBonuses bonus = ac.getBonuses(); + if (bonus != null) + bonus.addDamageShield(this, ds); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/DodgeEffectModifier.java b/src/engine/powers/effectmodifiers/DodgeEffectModifier.java new file mode 100644 index 00000000..027a3335 --- /dev/null +++ b/src/engine/powers/effectmodifiers/DodgeEffectModifier.java @@ -0,0 +1,57 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class DodgeEffectModifier extends AbstractEffectModifier { + + public DodgeEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/DurabilityEffectModifier.java b/src/engine/powers/effectmodifiers/DurabilityEffectModifier.java new file mode 100644 index 00000000..d69f9f6a --- /dev/null +++ b/src/engine/powers/effectmodifiers/DurabilityEffectModifier.java @@ -0,0 +1,52 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class DurabilityEffectModifier extends AbstractEffectModifier { + + public DurabilityEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + if (item == null) { + return; + } + float amount = 0; + amount = this.percentMod; + item.addBonus(this, amount); + + + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ExclusiveDamageCapEffectModifier.java b/src/engine/powers/effectmodifiers/ExclusiveDamageCapEffectModifier.java new file mode 100644 index 00000000..1e53ae89 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ExclusiveDamageCapEffectModifier.java @@ -0,0 +1,48 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; + +public class ExclusiveDamageCapEffectModifier extends AbstractEffectModifier { + + public ExclusiveDamageCapEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + if (bonus == null) + return; + if (bonus.getList(this.modType) == null) + bonus.setList(this.modType, new HashSet<>()); + bonus.getList(this.modType).add(this.sourceType); + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/FadeEffectModifier.java b/src/engine/powers/effectmodifiers/FadeEffectModifier.java new file mode 100644 index 00000000..ede4cad9 --- /dev/null +++ b/src/engine/powers/effectmodifiers/FadeEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class FadeEffectModifier extends AbstractEffectModifier { + + public FadeEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/FlyEffectModifier.java b/src/engine/powers/effectmodifiers/FlyEffectModifier.java new file mode 100644 index 00000000..a3dc0a49 --- /dev/null +++ b/src/engine/powers/effectmodifiers/FlyEffectModifier.java @@ -0,0 +1,42 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class FlyEffectModifier extends AbstractEffectModifier { + + public FlyEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/HealthEffectModifier.java b/src/engine/powers/effectmodifiers/HealthEffectModifier.java new file mode 100644 index 00000000..3ddaf96b --- /dev/null +++ b/src/engine/powers/effectmodifiers/HealthEffectModifier.java @@ -0,0 +1,334 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.DamageType; +import engine.Enum.GameObjectType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.ChatManager; +import engine.jobs.AbstractEffectJob; +import engine.jobs.DamageOverTimeJob; +import engine.net.AbstractNetMsg; +import engine.net.DispatchMessage; +import engine.net.client.msg.ModifyHealthKillMsg; +import engine.net.client.msg.ModifyHealthMsg; +import engine.objects.*; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; + +public class HealthEffectModifier extends AbstractEffectModifier { + + private DamageType damageType; + + public HealthEffectModifier(ResultSet rs) throws SQLException { + super(rs); + String damageTypeDB = rs.getString("type"); + try { + this.damageType = DamageType.valueOf(damageTypeDB); + } catch (IllegalArgumentException e) { + Logger.error("DamageType could not be loaded from database. " + "UUID = " + this.UUID + + " value received = '" + damageTypeDB + '\'', e); + } + } + + public static float getMinDamage(float baseMin, float intelligence, float spirit, float focus) { + float min = baseMin * (((float) Math.pow(intelligence, 0.75f) * 0.0311f) + (0.02f * (int) focus) + ((float) Math.pow(spirit, 0.75f) * 0.0416f)); + return (float) ((int) (min + 0.5f)); //round to nearest whole number + } + + public static float getMaxDamage(float baseMax, float intelligence, float spirit, float focus) { + float max = baseMax * (((float) Math.pow(intelligence, 0.75f) * 0.0785f) + (0.015f * (int) focus) + ((float) Math.pow(spirit, 0.75f) * 0.0157f)); + return (float) ((int) (max + 0.5f)); //round to nearest whole number + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + if (awo == null) { + Logger.error("_applyEffectModifier(): NULL AWO passed in."); + return; + } + + if (effect == null) { + Logger.error("_applyEffectModifier(): NULL AbstractEffectJob passed in."); + return; + } + + float modAmount = 0f; + + // Modify health by percent + if (this.percentMod != 0f) { + + //high level mobs/players should not be %damaged/healed. + if (awo.getHealthMax() > 25000f && (this.percentMod < 0f || this.percentMod > 5f)) + return; + + float mod = 1f; + if (this.useRampAdd) + mod = (this.percentMod + (this.ramp * trains)) / 100; + else + mod = (this.percentMod * (1 + (this.ramp * trains))) / 100; + modAmount = mod * awo.getHealthMax(); + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + if (((AbstractCharacter) awo).isSit()) + modAmount *= 2.5f; + } + + //debug for spell damage and atr + if (source.getDebug(16) && source.getObjectType().equals(GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + String smsg = "Percent Damage: " + mod * 100 + '%'; + ChatManager.chatSystemInfo(pc, smsg); + } + } + + // Modify health by min/max amount + else if (this.minMod != 0f || this.maxMod != 0f) { + float min = this.minMod; + float max = this.maxMod; + if (this.ramp > 0f) { + float mod = this.ramp * trains; + if (this.useRampAdd) { + min += mod; + max += mod; + } else { + min *= (1 + mod); + max *= (1 + mod); + } + } + if (source.getObjectType().equals(GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + + float focus; + CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName()); + if (skill == null) + focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName()); + else + focus = skill.getModifiedAmount(); + //TODO clean up old formulas once new one is verified + // min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus); + // max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus); + float intt = (pc.getStatIntCurrent() >= 1) ? (float) pc.getStatIntCurrent() : 1f; + float spi = (pc.getStatSpiCurrent() >= 1) ? (float) pc.getStatSpiCurrent() : 1f; + // min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus); + // max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus); + min = HealthEffectModifier.getMinDamage(min, intt, spi, focus); + max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus); + + //debug for spell damage and atr + if (pc.getDebug(16)) { + String smsg = "Damage: " + (int) Math.abs(min) + " - " + (int) Math.abs(max); + ChatManager.chatSystemInfo(pc, smsg); + } + } else if (source.getObjectType() == GameObjectType.Mob) { + Mob pc = (Mob) source; + + float focus; + CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName()); + if (skill == null) + focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName()); + else + focus = skill.getModifiedAmount(); + //TODO clean up old formulas once new one is verified + // min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus); + // max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus); + float intt = (pc.getStatIntCurrent() >= 1) ? (float) pc.getStatIntCurrent() : 1f; + + if (pc.isPlayerGuard()) + intt = 200; + float spi = (pc.getStatSpiCurrent() >= 1) ? (float) pc.getStatSpiCurrent() : 1f; + + if (pc.isPlayerGuard()) + spi = 200; + // min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus); + // max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus); + min = HealthEffectModifier.getMinDamage(min, intt, spi, focus); + max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus); + + //debug for spell damage and atr + // if (pc.getDebug(16)) { + // String smsg = "Damage: " + (int)Math.abs(min) + " - " + (int)Math.abs(max); + // ChatManager.chatSystemInfo(pc, smsg); + // } + } + modAmount = calculateDamage(source, min, max, awo, trains); + PlayerBonuses bonus = source.getBonuses(); + + // Apply any power effect modifiers (such as stances) + if (bonus != null) + modAmount *= (1 + (bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None))); + } + if (modAmount == 0f) + return; + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + + if (!ac.isAlive()) + return; + + int powerID = 0, effectID = 0; + String powerName = ""; + if (effect.getPower() != null) { + powerID = effect.getPower().getToken(); + powerName = effect.getPower().getName(); + } else { + Logger.error("Power has returned null! Damage will fail to register! (" + (ac.getCurrentHitpoints() > 0 ? "Alive)" : "Dead)")); + } + + if (effect.getEffect() != null) { + effectID = effect.getEffect().getToken(); + } else { + Logger.error("Effect has returned null! Damage will fail to register! (" + (ac.getCurrentHitpoints() > 0 ? "Alive)" : "Dead)")); + } + + //see if target is immune to heals + if (modAmount > 0f) { + boolean skipImmune = false; + // first tick of HoT going thru SM was removed in a later patch + /*if (effect.getAction().getPowerAction() instanceof DirectDamagePowerAction) { + ArrayList actions = effect.getPower().getActions(); + for (ActionsBase ab : actions) { + AbstractPowerAction apa = ab.getPowerAction(); + if (apa instanceof DamageOverTimePowerAction) + skipImmune = true; + } + }*/ + + PlayerBonuses bonus = ac.getBonuses(); + if (!skipImmune && bonus.getFloat(ModType.BlackMantle, SourceType.Heal) >= trains) { + ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, 0f, powerID, powerName, trains, effectID); + mhm.setUnknown03(5); //set target is immune + DispatchMessage.sendToAllInRange(ac, mhm); + return; + } + } + float mod = 0; + + //Modify health + + mod = ac.modifyHealth(modAmount, source, false); + + float cur = awo.getCurrentHitpoints(); + float maxAmount = awo.getHealthMax() - cur; + + AbstractNetMsg mhm = null; + if (modAmount < 0 && cur < 0 && mod != 0) + mhm = new ModifyHealthKillMsg(source, ac, modAmount, 0f, 0f, powerID, powerName, trains, effectID); + else + mhm = new ModifyHealthMsg(source, ac, modAmount, 0f, 0f, powerID, powerName, trains, effectID); + + if (effect instanceof DamageOverTimeJob) { + if (mhm instanceof ModifyHealthMsg) + ((ModifyHealthMsg) mhm).setOmitFromChat(1); + else if (mhm instanceof ModifyHealthKillMsg) + ((ModifyHealthKillMsg) mhm).setUnknown02(1); + } + + //send the damage + + DispatchMessage.sendToAllInRange(ac, mhm); + + // //send corpse if this kills a mob + // //TODO fix the someone misses blurb. + // if(awo instanceof Mob && awo.getHealth() <= 0) { + // CombatMessageMsg cmm = new CombatMessageMsg(null, 0, awo, 15); + // try { + // DispatchMessage.sendToAllInRange(ac, cmm); + // } catch (MsgSendException e) { + // Logger.error("MobCorpseSendError", e); + // } + // } + } else if (awo.getObjectType().equals(GameObjectType.Building)) { + + Building b = (Building) awo; + + if (modAmount < 0 && (!b.isVulnerable())) + return; //can't damage invul building + + int powerID = 0, effectID = 0; + String powerName = ""; + if (effect.getPower() != null) { + powerID = effect.getPower().getToken(); + powerName = effect.getPower().getName(); + } else + Logger.error("Power has returned null! Damage will fail to register! (" + (b.getRank() == -1 ? "Standing)" : "Destroyed)")); + + if (effect.getEffect() != null) { + effectID = effect.getEffect().getToken(); + } else + Logger.error("Effect has returned null! Damage will fail to register! (" + (b.getRank() == -1 ? "Standing)" : "Destroyed)")); + + float mod = b.modifyHealth(modAmount, source); + ModifyHealthMsg mhm = new ModifyHealthMsg(source, b, modAmount, 0f, 0f, powerID, powerName, trains, effectID); + + if (effect instanceof DamageOverTimeJob) + mhm.setOmitFromChat(1); + + //send the damage + + DispatchMessage.sendToAllInRange(b, mhm); + + } + } + + private float calculateDamage(AbstractCharacter source, float minDamage, float maxDamage, AbstractWorldObject awo, int trains) { + + // get range between min and max + float range = maxDamage - minDamage; + + // Damage is calculated twice to average a more central point + float damage = ThreadLocalRandom.current().nextFloat() * range; + damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + + // put it back between min and max + damage += minDamage; + + Resists resists = null; + // get resists + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + resists = ac.getResists(); + } else if (awo.getObjectType().equals(GameObjectType.Building)) + resists = ((Building) awo).getResists(); + + // calculate resists in if any + if (resists != null) { + if (AbstractWorldObject.IsAbstractCharacter(awo)) + damage = resists.getResistedDamage(source, (AbstractCharacter) awo, damageType, damage * -1, trains) * -1; + else + damage = resists.getResistedDamage(source, null, damageType, damage * -1, trains) * -1; + } + + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + if (ac.isSit()) + damage *= 2.5f; // increase damage if sitting + } + + return damage; + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } + +} diff --git a/src/engine/powers/effectmodifiers/HealthFullEffectModifier.java b/src/engine/powers/effectmodifiers/HealthFullEffectModifier.java new file mode 100644 index 00000000..4dc68b0a --- /dev/null +++ b/src/engine/powers/effectmodifiers/HealthFullEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class HealthFullEffectModifier extends AbstractEffectModifier { + + public HealthFullEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/HealthRecoverRateEffectModifier.java b/src/engine/powers/effectmodifiers/HealthRecoverRateEffectModifier.java new file mode 100644 index 00000000..f657a765 --- /dev/null +++ b/src/engine/powers/effectmodifiers/HealthRecoverRateEffectModifier.java @@ -0,0 +1,50 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class HealthRecoverRateEffectModifier extends AbstractEffectModifier { + + public HealthRecoverRateEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + ac.update(); + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.multRegen(this.modType, amount); //positive regen modifiers + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/IgnoreDamageCapEffectModifier.java b/src/engine/powers/effectmodifiers/IgnoreDamageCapEffectModifier.java new file mode 100644 index 00000000..6eb71af7 --- /dev/null +++ b/src/engine/powers/effectmodifiers/IgnoreDamageCapEffectModifier.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; + +public class IgnoreDamageCapEffectModifier extends AbstractEffectModifier { + + public IgnoreDamageCapEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + if (bonus == null) + return; + + if (bonus.getList(this.modType) == null) + bonus.setList(this.modType, new HashSet<>()); + bonus.getList(this.modType).add(this.sourceType); + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/IgnorePassiveDefenseEffectModifier.java b/src/engine/powers/effectmodifiers/IgnorePassiveDefenseEffectModifier.java new file mode 100644 index 00000000..c4092bbd --- /dev/null +++ b/src/engine/powers/effectmodifiers/IgnorePassiveDefenseEffectModifier.java @@ -0,0 +1,42 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class IgnorePassiveDefenseEffectModifier extends AbstractEffectModifier { + + public IgnorePassiveDefenseEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ImmuneToAttackEffectModifier.java b/src/engine/powers/effectmodifiers/ImmuneToAttackEffectModifier.java new file mode 100644 index 00000000..003855f9 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ImmuneToAttackEffectModifier.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ImmuneToAttackEffectModifier extends AbstractEffectModifier { + + public ImmuneToAttackEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ImmuneToEffectModifier.java b/src/engine/powers/effectmodifiers/ImmuneToEffectModifier.java new file mode 100644 index 00000000..f237a800 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ImmuneToEffectModifier.java @@ -0,0 +1,42 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ImmuneToEffectModifier extends AbstractEffectModifier { + + public ImmuneToEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ImmuneToPowersEffectModifier.java b/src/engine/powers/effectmodifiers/ImmuneToPowersEffectModifier.java new file mode 100644 index 00000000..9ee5df4f --- /dev/null +++ b/src/engine/powers/effectmodifiers/ImmuneToPowersEffectModifier.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ImmuneToPowersEffectModifier extends AbstractEffectModifier { + + public ImmuneToPowersEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/InvisibleEffectModifier.java b/src/engine/powers/effectmodifiers/InvisibleEffectModifier.java new file mode 100644 index 00000000..5c358185 --- /dev/null +++ b/src/engine/powers/effectmodifiers/InvisibleEffectModifier.java @@ -0,0 +1,84 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum; +import engine.gameManager.SessionManager; +import engine.jobs.AbstractEffectJob; +import engine.net.client.ClientConnection; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class InvisibleEffectModifier extends AbstractEffectModifier { + + public InvisibleEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + if (awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) awo; + + if (effect == null) + return; + + PowersBase pb = effect.getPower(); + if (pb == null) + return; + + ActionsBase ab = effect.getAction(); + + if (ab == null) + return; + + //send invis message to everyone around. + ClientConnection origin = SessionManager.getClientConnection(pc); + if (origin == null) + return; + + ab.getDurationInSeconds(trains); + + pc.setHidden(trains); + + pc.setTimeStampNow("Invis"); + + } else { + Logger.error("Cannot go invis on a non player."); + } + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + if (ac == null) + return; + PlayerBonuses bonus = ac.getBonuses(); + if (bonus != null) + bonus.updateIfHigher(this, (float) trains); + + //remove pets + if (ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) + ((PlayerCharacter) ac).dismissPet(); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ItemNameEffectModifier.java b/src/engine/powers/effectmodifiers/ItemNameEffectModifier.java new file mode 100644 index 00000000..bf51fd3d --- /dev/null +++ b/src/engine/powers/effectmodifiers/ItemNameEffectModifier.java @@ -0,0 +1,146 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; +import engine.powers.EffectsBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ItemNameEffectModifier extends AbstractEffectModifier { + + public String name = ""; + + public ItemNameEffectModifier(ResultSet rs) throws SQLException { + super(rs); + + //We're going to add effect names to a lookup map for ./makeitem + int ID = rs.getInt("ID"); + switch (ID) { //don't add these ID's to the name list. They're duplicates + case 4259: + return; + case 4210: + return; + case 4: + return; + case 97: + return; + case 610: + return; + case 4442: + return; + case 5106: + return; + case 4637: + return; + case 2271: + return; + case 587: + return; + case 600: + return; + case 3191: + return; + case 3589: + return; + case 3950: + return; + case 3499: + return; + case 4925: + return; + case 15: + return; + case 5101: + return; + case 2418: + return; + case 183: + return; + case 373: + return; + case 1893: + return; + case 3127: + return; + case 1232: + return; + case 4522: + return; + case 4817: + return; + case 2833: + return; + case 4469: + return; + case 2122: + return; + case 3057: + return; + case 3070: + return; + case 191: + return; + case 3117: + return; + case 3702: + return; + case 1619: + return; + case 2584: + return; + case 414: + return; + case 2078: + return; + case 4844: + return; + case 2275: + return; + } + + String namePre = rs.getString("string1"); + String nameSuf = rs.getString("string2"); + String n = (namePre.isEmpty()) ? nameSuf : namePre; + this.name = n; + n = n.toLowerCase(); + n = n.replace(" ", "_"); + String IDString = rs.getString("IDString"); + IDString = IDString.substring(0, IDString.length() - 1); + EffectsBase.addItemEffectsByName(n, IDString); + } + + public String getName() { + return this.name; + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ManaEffectModifier.java b/src/engine/powers/effectmodifiers/ManaEffectModifier.java new file mode 100644 index 00000000..d70b9a2c --- /dev/null +++ b/src/engine/powers/effectmodifiers/ManaEffectModifier.java @@ -0,0 +1,223 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum; +import engine.Enum.DamageType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.ChatManager; +import engine.jobs.AbstractEffectJob; +import engine.jobs.DamageOverTimeJob; +import engine.net.DispatchMessage; +import engine.net.client.msg.ModifyHealthMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.poweractions.AbstractPowerAction; +import engine.powers.poweractions.DamageOverTimePowerAction; +import engine.powers.poweractions.DirectDamagePowerAction; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; + +public class ManaEffectModifier extends AbstractEffectModifier { + + private DamageType damageType; + + public ManaEffectModifier(ResultSet rs) throws SQLException { + super(rs); + String damageTypeDB = rs.getString("type"); + try { + this.damageType = DamageType.valueOf(damageTypeDB); + } catch (IllegalArgumentException e) { + Logger.error("DamageType could not be loaded from database. " + "UUID = " + this.UUID + + " value received = '" + damageTypeDB + '\'', e); + } + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + if (awo == null) { + Logger.error("_applyEffectModifier(): NULL AWO passed in."); + return; + } + + if (effect == null) { + Logger.error("_applyEffectModifier(): NULL AbstractEffectJob passed in."); + return; + } + + if (!AbstractWorldObject.IsAbstractCharacter(awo)) + return; + AbstractCharacter awoac = (AbstractCharacter) awo; + + float modAmount = 0f; + + // Modify Mana by percent + if (this.percentMod != 0f) { + + float mod = 1f; + if (this.useRampAdd) + mod = (this.percentMod + (this.ramp * trains)) / 100; + else + mod = (this.percentMod * (1 + (this.ramp * trains))) / 100; + modAmount = mod * awoac.getManaMax(); + + if (awoac.isSit()) + modAmount *= 2.5f; + + //debug for spell damage and atr + if (source.getDebug(16) && source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + + PlayerCharacter pc = (PlayerCharacter) source; + String smsg = "Percent Damage: " + mod * 100 + '%'; + + ChatManager.chatSystemInfo(pc, smsg); + } + } + + // Modify health by min/max amount + else if (this.minMod != 0f || this.maxMod != 0f) { + float min = this.minMod; + float max = this.maxMod; + if (this.ramp > 0f) { + float mod = this.ramp * trains; + if (this.useRampAdd) { + min += mod; + max += mod; + } else { + min *= (1 + mod); + max *= (1 + mod); + } + } + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + + float focus; + CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName()); + if (skill == null) + focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName()); + else + focus = skill.getModifiedAmount(); + //TODO clean up old formulas once new one is verified + // min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus); + // max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus); + float intt = (pc.getStatIntCurrent() >= 1) ? (float) pc.getStatIntCurrent() : 1f; + float spi = (pc.getStatSpiCurrent() >= 1) ? (float) pc.getStatSpiCurrent() : 1f; + // min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus); + // max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus); + min = HealthEffectModifier.getMinDamage(min, intt, spi, focus); + max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus); + + //debug for spell damage and atr + if (pc.getDebug(16)) { + String smsg = "Damage: " + (int) Math.abs(min) + " - " + (int) Math.abs(max); + ChatManager.chatSystemInfo(pc, smsg); + } + } + modAmount = calculateDamage(source, awoac, min, max, awo, trains); + PlayerBonuses bonus = source.getBonuses(); + + // Apply any power effect modifiers (such as stances) + if (bonus != null) + modAmount *= (1 + bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None)); + } + if (modAmount == 0f) + return; + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + int powerID = 0, effectID = 0; + String powerName = ""; + if (effect.getPower() != null) { + powerID = effect.getPower().getToken(); + powerName = effect.getPower().getName(); + } + if (effect.getEffect() != null) { + effectID = effect.getEffect().getToken(); + } + + //see if target is immune to heals + if (modAmount > 0f) { + boolean skipImmune = false; + if (effect.getAction().getPowerAction() instanceof DirectDamagePowerAction) { + ArrayList actions = effect.getPower().getActions(); + for (ActionsBase ab : actions) { + AbstractPowerAction apa = ab.getPowerAction(); + if (apa instanceof DamageOverTimePowerAction) + skipImmune = true; + } + } + PlayerBonuses bonus = ac.getBonuses(); + if (!skipImmune && bonus.getFloat(ModType.BlackMantle, SourceType.Heal) >= trains) { + ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, 0f, powerID, powerName, trains, effectID); + mhm.setUnknown03(5); //set target is immune + DispatchMessage.sendToAllInRange(ac, mhm); + return; + } + } + + ac.modifyMana(modAmount, source); + + ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, modAmount, 0f, powerID, powerName, trains, + effectID); + if (effect instanceof DamageOverTimeJob) + mhm.setOmitFromChat(1); + DispatchMessage.sendToAllInRange(ac, mhm); + } + } + + private float calculateDamage(AbstractCharacter source, AbstractCharacter target, float minDamage, float maxDamage, AbstractWorldObject awo, int trains) { + // get range between min and max + float range = maxDamage - minDamage; + + // Damage is calculated twice to average a more central point + float damage = ThreadLocalRandom.current().nextFloat() * range; + damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + + // put it back between min and max + damage += minDamage; + + Resists resists = null; + // get resists + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + resists = ac.getResists(); + } else if (awo.getObjectType().equals(Enum.GameObjectType.Building)) + resists = ((Building) awo).getResists(); + + // calculate resists in if any + if (resists != null) + damage = resists.getResistedDamage(source, target, damageType, damage * -1, trains) * -1; + + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + if (ac.isSit()) + damage *= 2.5f; // increase damage if sitting + } + + return damage; + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ManaFullEffectModifier.java b/src/engine/powers/effectmodifiers/ManaFullEffectModifier.java new file mode 100644 index 00000000..ad1fa6b4 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ManaFullEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ManaFullEffectModifier extends AbstractEffectModifier { + + public ManaFullEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ManaRecoverRateEffectModifier.java b/src/engine/powers/effectmodifiers/ManaRecoverRateEffectModifier.java new file mode 100644 index 00000000..7c657e61 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ManaRecoverRateEffectModifier.java @@ -0,0 +1,48 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ManaRecoverRateEffectModifier extends AbstractEffectModifier { + + public ManaRecoverRateEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.multRegen(this.modType, amount); //positive regen modifiers + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/MaxDamageEffectModifier.java b/src/engine/powers/effectmodifiers/MaxDamageEffectModifier.java new file mode 100644 index 00000000..dafe263a --- /dev/null +++ b/src/engine/powers/effectmodifiers/MaxDamageEffectModifier.java @@ -0,0 +1,63 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MaxDamageEffectModifier extends AbstractEffectModifier { + + public MaxDamageEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + if (item == null) + return; + String key; + float amount = 0f; + if (this.percentMod != 0f) { + if (this.useRampAdd) + amount = (this.percentMod + (this.ramp * trains)) / 100f; + else + amount = (this.percentMod * (1 + (this.ramp * trains))) / 100f; + amount = amount / 100; + key = "max.percent"; + } else { + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + key = "max"; + } + item.addBonus(this, amount); + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/MeleeDamageEffectModifier.java b/src/engine/powers/effectmodifiers/MeleeDamageEffectModifier.java new file mode 100644 index 00000000..156cc502 --- /dev/null +++ b/src/engine/powers/effectmodifiers/MeleeDamageEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MeleeDamageEffectModifier extends AbstractEffectModifier { + + public MeleeDamageEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/MinDamageEffectModifier.java b/src/engine/powers/effectmodifiers/MinDamageEffectModifier.java new file mode 100644 index 00000000..7eeae151 --- /dev/null +++ b/src/engine/powers/effectmodifiers/MinDamageEffectModifier.java @@ -0,0 +1,63 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MinDamageEffectModifier extends AbstractEffectModifier { + + public MinDamageEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + if (item == null) + return; + String key; + float amount = 0f; + if (this.percentMod != 0f) { + if (this.useRampAdd) + amount = (this.percentMod + (this.ramp * trains)) / 100f; + else + amount = (this.percentMod * (1 + (this.ramp * trains))) / 100f; + amount = amount / 100; + key = "min.percent"; + } else { + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + key = "min"; + } + item.addBonus(this, amount); + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/NoModEffectModifier.java b/src/engine/powers/effectmodifiers/NoModEffectModifier.java new file mode 100644 index 00000000..a09c5600 --- /dev/null +++ b/src/engine/powers/effectmodifiers/NoModEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.GameObjectType; +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class NoModEffectModifier extends AbstractEffectModifier { + + public NoModEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + //TODO check if anything needs removed. + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + + switch (this.sourceType) { + case Fly: + if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter)) + return; + PlayerCharacter flyer = (PlayerCharacter) ac; + + if (flyer.getAltitude() > 0) + flyer.update(); + PlayerCharacter.GroundPlayer(flyer); + break; + + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/OCVEffectModifier.java b/src/engine/powers/effectmodifiers/OCVEffectModifier.java new file mode 100644 index 00000000..5d6cdc7b --- /dev/null +++ b/src/engine/powers/effectmodifiers/OCVEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class OCVEffectModifier extends AbstractEffectModifier { + + public OCVEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ParryEffectModifier.java b/src/engine/powers/effectmodifiers/ParryEffectModifier.java new file mode 100644 index 00000000..386e9f5c --- /dev/null +++ b/src/engine/powers/effectmodifiers/ParryEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ParryEffectModifier extends AbstractEffectModifier { + + public ParryEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/PassiveDefenseEffectModifier.java b/src/engine/powers/effectmodifiers/PassiveDefenseEffectModifier.java new file mode 100644 index 00000000..b9ad4bb6 --- /dev/null +++ b/src/engine/powers/effectmodifiers/PassiveDefenseEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class PassiveDefenseEffectModifier extends AbstractEffectModifier { + + public PassiveDefenseEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/PowerCostEffectModifier.java b/src/engine/powers/effectmodifiers/PowerCostEffectModifier.java new file mode 100644 index 00000000..57c8216e --- /dev/null +++ b/src/engine/powers/effectmodifiers/PowerCostEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class PowerCostEffectModifier extends AbstractEffectModifier { + + public PowerCostEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/PowerCostHealthEffectModifier.java b/src/engine/powers/effectmodifiers/PowerCostHealthEffectModifier.java new file mode 100644 index 00000000..ad80fdf7 --- /dev/null +++ b/src/engine/powers/effectmodifiers/PowerCostHealthEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class PowerCostHealthEffectModifier extends AbstractEffectModifier { + + public PowerCostHealthEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/PowerDamageEffectModifier.java b/src/engine/powers/effectmodifiers/PowerDamageEffectModifier.java new file mode 100644 index 00000000..336bda4e --- /dev/null +++ b/src/engine/powers/effectmodifiers/PowerDamageEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class PowerDamageEffectModifier extends AbstractEffectModifier { + + public PowerDamageEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ProtectionFromEffectModifier.java b/src/engine/powers/effectmodifiers/ProtectionFromEffectModifier.java new file mode 100644 index 00000000..2b32aa95 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ProtectionFromEffectModifier.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ProtectionFromEffectModifier extends AbstractEffectModifier { + + public ProtectionFromEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + if (bonus == null) + return; + bonus.setFloat(this, trains); + // bonus.setBool(this, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ResistanceEffectModifier.java b/src/engine/powers/effectmodifiers/ResistanceEffectModifier.java new file mode 100644 index 00000000..341268e1 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ResistanceEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ResistanceEffectModifier extends AbstractEffectModifier { + + public ResistanceEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ScaleHeightEffectModifier.java b/src/engine/powers/effectmodifiers/ScaleHeightEffectModifier.java new file mode 100644 index 00000000..e9872926 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ScaleHeightEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ScaleHeightEffectModifier extends AbstractEffectModifier { + + public ScaleHeightEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ScaleWidthEffectModifier.java b/src/engine/powers/effectmodifiers/ScaleWidthEffectModifier.java new file mode 100644 index 00000000..40339310 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ScaleWidthEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ScaleWidthEffectModifier extends AbstractEffectModifier { + + public ScaleWidthEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ScanRangeEffectModifier.java b/src/engine/powers/effectmodifiers/ScanRangeEffectModifier.java new file mode 100644 index 00000000..0b5f34a4 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ScanRangeEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ScanRangeEffectModifier extends AbstractEffectModifier { + + public ScanRangeEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/SeeInvisibleEffectModifier.java b/src/engine/powers/effectmodifiers/SeeInvisibleEffectModifier.java new file mode 100644 index 00000000..ae779788 --- /dev/null +++ b/src/engine/powers/effectmodifiers/SeeInvisibleEffectModifier.java @@ -0,0 +1,45 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SeeInvisibleEffectModifier extends AbstractEffectModifier { + + public SeeInvisibleEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + if (ac == null) + return; + PlayerBonuses bonus = ac.getBonuses(); + if (bonus != null) + bonus.updateIfHigher(this, (float) trains); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/SilencedEffectModifier.java b/src/engine/powers/effectmodifiers/SilencedEffectModifier.java new file mode 100644 index 00000000..0bb0577b --- /dev/null +++ b/src/engine/powers/effectmodifiers/SilencedEffectModifier.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SilencedEffectModifier extends AbstractEffectModifier { + + public SilencedEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/SkillEffectModifier.java b/src/engine/powers/effectmodifiers/SkillEffectModifier.java new file mode 100644 index 00000000..1f3928c3 --- /dev/null +++ b/src/engine/powers/effectmodifiers/SkillEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SkillEffectModifier extends AbstractEffectModifier { + + public SkillEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/SlayEffectModifier.java b/src/engine/powers/effectmodifiers/SlayEffectModifier.java new file mode 100644 index 00000000..9c149bdc --- /dev/null +++ b/src/engine/powers/effectmodifiers/SlayEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SlayEffectModifier extends AbstractEffectModifier { + + public SlayEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/SpeedEffectModifier.java b/src/engine/powers/effectmodifiers/SpeedEffectModifier.java new file mode 100644 index 00000000..153dad7a --- /dev/null +++ b/src/engine/powers/effectmodifiers/SpeedEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SpeedEffectModifier extends AbstractEffectModifier { + + public SpeedEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + //Logger.error(this.getSimpleClassName(), "Speed applied with " + trains + " trains"); + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/SpireBlockEffectModifier.java b/src/engine/powers/effectmodifiers/SpireBlockEffectModifier.java new file mode 100644 index 00000000..7cd3620f --- /dev/null +++ b/src/engine/powers/effectmodifiers/SpireBlockEffectModifier.java @@ -0,0 +1,42 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SpireBlockEffectModifier extends AbstractEffectModifier { + + public SpireBlockEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/StaminaEffectModifier.java b/src/engine/powers/effectmodifiers/StaminaEffectModifier.java new file mode 100644 index 00000000..59145f16 --- /dev/null +++ b/src/engine/powers/effectmodifiers/StaminaEffectModifier.java @@ -0,0 +1,221 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum; +import engine.Enum.DamageType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.ChatManager; +import engine.jobs.AbstractEffectJob; +import engine.jobs.DamageOverTimeJob; +import engine.net.DispatchMessage; +import engine.net.client.msg.ModifyHealthMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.poweractions.AbstractPowerAction; +import engine.powers.poweractions.DamageOverTimePowerAction; +import engine.powers.poweractions.DirectDamagePowerAction; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; + +public class StaminaEffectModifier extends AbstractEffectModifier { + + private DamageType damageType; + + public StaminaEffectModifier(ResultSet rs) throws SQLException { + super(rs); + String damageTypeDB = rs.getString("type"); + try { + this.damageType = DamageType.valueOf(damageTypeDB); + } catch (IllegalArgumentException e) { + Logger.error("DamageType could not be loaded from database. " + "UUID = " + this.UUID + + " value received = '" + damageTypeDB + '\'', e); + } + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + if (awo == null) { + Logger.error("_applyEffectModifier(): NULL AWO passed in."); + return; + } + + if (effect == null) { + Logger.error("_applyEffectModifier(): NULL AbstractEffectJob passed in."); + return; + } + + if (!AbstractWorldObject.IsAbstractCharacter(awo)) + return; + AbstractCharacter awoac = (AbstractCharacter) awo; + + float modAmount = 0f; + + // Modify Stamina by percent + if (this.percentMod != 0f) { + float mod = 1f; + if (this.useRampAdd) + mod = (this.percentMod + (this.ramp * trains)) / 100; + else + mod = (this.percentMod * (1 + (this.ramp * trains))) / 100; + modAmount = mod * awoac.getStaminaMax(); + if (awoac.isSit()) + modAmount *= 2.5f; + + //debug for spell damage and atr + if (source.getDebug(16) && source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + String smsg = "Percent Damage: " + mod * 100 + '%'; + ChatManager.chatSystemInfo(pc, smsg); + } + } + + // Modify Stamina by min/max amount + else if (this.minMod != 0f || this.maxMod != 0f) { + float min = this.minMod; + float max = this.maxMod; + if (this.ramp > 0f) { + float mod = this.ramp * trains; + if (this.useRampAdd) { + min += mod; + max += mod; + } else { + min *= (1 + mod); + max *= (1 + mod); + } + } + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + + float focus; + CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName()); + if (skill == null) + focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName()); + else + focus = skill.getModifiedAmount(); + //TODO clean up old formulas once new one is verified + // min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus); + // max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus); + float intt = (pc.getStatIntCurrent() >= 1) ? (float) pc.getStatIntCurrent() : 1f; + float spi = (pc.getStatSpiCurrent() >= 1) ? (float) pc.getStatSpiCurrent() : 1f; + // min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus); + // max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus); + min = HealthEffectModifier.getMinDamage(min, intt, spi, focus); + max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus); + + //debug for spell damage and atr + if (pc.getDebug(16)) { + String smsg = "Damage: " + (int) Math.abs(min) + " - " + (int) Math.abs(max); + ChatManager.chatSystemInfo(pc, smsg); + } + } + modAmount = calculateDamage(source, awoac, min, max, awo, trains); + PlayerBonuses bonus = source.getBonuses(); + + // Apply any power effect modifiers (such as stances) + if (bonus != null) + modAmount *= (1 + (bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None))); + } + if (modAmount == 0f) + return; + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + int powerID = 0, effectID = 0; + String powerName = ""; + if (effect.getPower() != null) { + powerID = effect.getPower().getToken(); + powerName = effect.getPower().getName(); + } + if (effect.getEffect() != null) { + effectID = effect.getEffect().getToken(); + } + + //see if target is immune to heals + if (modAmount > 0f) { + boolean skipImmune = false; + if (effect.getAction().getPowerAction() instanceof DirectDamagePowerAction) { + ArrayList actions = effect.getPower().getActions(); + for (ActionsBase ab : actions) { + AbstractPowerAction apa = ab.getPowerAction(); + if (apa instanceof DamageOverTimePowerAction) + skipImmune = true; + } + } + PlayerBonuses bonus = ac.getBonuses(); + if (!skipImmune && bonus.getFloat(ModType.BlackMantle, SourceType.Heal) >= trains) { + ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, 0f, powerID, powerName, trains, effectID); + mhm.setUnknown03(5); //set target is immune + DispatchMessage.sendToAllInRange(ac, mhm); + + return; + } + } + + ac.modifyStamina(modAmount, source); + + ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, modAmount, powerID, powerName, trains, + effectID); + if (effect instanceof DamageOverTimeJob) + mhm.setOmitFromChat(1); + DispatchMessage.sendToAllInRange(ac, mhm); + } + } + + private float calculateDamage(AbstractCharacter source, AbstractCharacter target, float minDamage, float maxDamage, AbstractWorldObject awo, int trains) { + + // get range between min and max + float range = maxDamage - minDamage; + + // Damage is calculated twice to average a more central point + float damage = ThreadLocalRandom.current().nextFloat() * range; + damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + + // put it back between min and max + damage += minDamage; + + Resists resists = null; + // get resists + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + resists = ac.getResists(); + } else if (awo.getObjectType().equals(Enum.GameObjectType.Building)) + resists = ((Building) awo).getResists(); + + // calculate resists in if any + if (resists != null) + damage = resists.getResistedDamage(source, target, damageType, damage * -1, trains) * -1; + + if (AbstractWorldObject.IsAbstractCharacter(awo)) { + AbstractCharacter ac = (AbstractCharacter) awo; + if (ac.isSit()) + damage *= 2.5f; // increase damage if sitting + } + + return damage; + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/StaminaFullEffectModifier.java b/src/engine/powers/effectmodifiers/StaminaFullEffectModifier.java new file mode 100644 index 00000000..475ce82a --- /dev/null +++ b/src/engine/powers/effectmodifiers/StaminaFullEffectModifier.java @@ -0,0 +1,56 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class StaminaFullEffectModifier extends AbstractEffectModifier { + + public StaminaFullEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/StaminaRecoverRateEffectModifier.java b/src/engine/powers/effectmodifiers/StaminaRecoverRateEffectModifier.java new file mode 100644 index 00000000..b2847a8c --- /dev/null +++ b/src/engine/powers/effectmodifiers/StaminaRecoverRateEffectModifier.java @@ -0,0 +1,50 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class StaminaRecoverRateEffectModifier extends AbstractEffectModifier { + + public StaminaRecoverRateEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + + //Erection is this right? + amount = amount / 100; + bonus.multRegen(this.modType, amount); //positive regen modifiers + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/StunnedEffectModifier.java b/src/engine/powers/effectmodifiers/StunnedEffectModifier.java new file mode 100644 index 00000000..0789f3c7 --- /dev/null +++ b/src/engine/powers/effectmodifiers/StunnedEffectModifier.java @@ -0,0 +1,50 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.Enum.GameObjectType; +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class StunnedEffectModifier extends AbstractEffectModifier { + + public StunnedEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + if (ac.getObjectType() == GameObjectType.Mob) { + Mob mob = (Mob) ac; + } + + PlayerBonuses bonus = ac.getBonuses(); + bonus.setBool(this.modType, this.sourceType, true); + ac.cancelOnStun(); + ac.setIsCasting(false); + ac.stopMovement(ac.getLoc()); + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/ValueEffectModifier.java b/src/engine/powers/effectmodifiers/ValueEffectModifier.java new file mode 100644 index 00000000..6b68cc83 --- /dev/null +++ b/src/engine/powers/effectmodifiers/ValueEffectModifier.java @@ -0,0 +1,44 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ValueEffectModifier extends AbstractEffectModifier { + + public ValueEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/WeaponProcEffectModifier.java b/src/engine/powers/effectmodifiers/WeaponProcEffectModifier.java new file mode 100644 index 00000000..5ef4251b --- /dev/null +++ b/src/engine/powers/effectmodifiers/WeaponProcEffectModifier.java @@ -0,0 +1,50 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.gameManager.PowersManager; +import engine.jobs.AbstractEffectJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WeaponProcEffectModifier extends AbstractEffectModifier { + + public WeaponProcEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } + + public void applyProc(AbstractCharacter ac, AbstractWorldObject target) { + PowersManager.applyPower(ac, target, Vector3fImmutable.ZERO, this.string1, (int) this.percentMod, false); + } +} diff --git a/src/engine/powers/effectmodifiers/WeaponRangeEffectModifier.java b/src/engine/powers/effectmodifiers/WeaponRangeEffectModifier.java new file mode 100644 index 00000000..52d21e2a --- /dev/null +++ b/src/engine/powers/effectmodifiers/WeaponRangeEffectModifier.java @@ -0,0 +1,57 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WeaponRangeEffectModifier extends AbstractEffectModifier { + + public WeaponRangeEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + Float amount = 0f; + PlayerBonuses bonus = ac.getBonuses(); + if (this.percentMod != 0f) { //Stat Percent Modifiers + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + bonus.addFloat(this, amount); + } else { //Stat Modifiers + if (this.useRampAdd) + amount = this.minMod + (this.ramp * trains); + else + amount = this.minMod * (1 + (this.ramp * trains)); + bonus.addFloat(this, amount); + } + + } + + @Override + public void applyBonus(Item item, int trains) { + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/effectmodifiers/WeaponSpeedEffectModifier.java b/src/engine/powers/effectmodifiers/WeaponSpeedEffectModifier.java new file mode 100644 index 00000000..b39958e0 --- /dev/null +++ b/src/engine/powers/effectmodifiers/WeaponSpeedEffectModifier.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.effectmodifiers; + +import engine.jobs.AbstractEffectJob; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Item; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WeaponSpeedEffectModifier extends AbstractEffectModifier { + + public WeaponSpeedEffectModifier(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) { + + } + + @Override + public void applyBonus(AbstractCharacter ac, int trains) { + + Float amount = 0f; + + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + + amount = amount / 100; + + ac.getBonuses().addFloat(this, amount); + } + + @Override + public void applyBonus(Item item, int trains) { + Float amount = 0f; + if (this.useRampAdd) + amount = this.percentMod + (this.ramp * trains); + else + amount = this.percentMod * (1 + (this.ramp * trains)); + amount = amount / 100; + item.addBonus(this, amount); + } + + @Override + public void applyBonus(Building building, int trains) { + } +} diff --git a/src/engine/powers/poweractions/AbstractPowerAction.java b/src/engine/powers/poweractions/AbstractPowerAction.java new file mode 100644 index 00000000..5f8f2131 --- /dev/null +++ b/src/engine/powers/poweractions/AbstractPowerAction.java @@ -0,0 +1,306 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.gameManager.DbManager; +import engine.gameManager.PowersManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Item; +import engine.objects.PreparedStatementShared; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public abstract class AbstractPowerAction { + + protected PowersBase parent; + protected int UUID; + protected String IDString; + protected String type; + protected boolean isAggressive; + protected long validItemFlags; + + /** + * No Table ID Constructor + */ + public AbstractPowerAction() { + + } + + /** + * ResultSet Constructor + */ + public AbstractPowerAction(ResultSet rs) throws SQLException { + + this.UUID = rs.getInt("ID"); + this.IDString = rs.getString("IDString"); + this.type = rs.getString("type"); + int flags = rs.getInt("flags"); + this.isAggressive = ((flags & 128) != 0) ? true : false; + } + + public AbstractPowerAction(int uUID, String iDString, String type, boolean isAggressive, + long validItemFlags) { + super(); + UUID = uUID; + IDString = iDString; + this.type = type; + this.isAggressive = false; + } + + public static void getAllPowerActions(HashMap powerActions, HashMap powerActionsByID, HashMap effects) { + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM static_power_poweraction"); + ResultSet rs = ps.executeQuery(); + String IDString, type; + while (rs.next()) { + AbstractPowerAction apa; + type = rs.getString("type"); + IDString = rs.getString("IDString"); + int token = DbManager.hasher.SBStringHash(IDString); + //cache token, used for applying effects. + PowersManager.ActionTokenByIDString.put(IDString, token); + apa = null; + switch (type) { + default: + Logger.error("valid type not found for poweraction of ID" + rs.getInt("ID")); + break; + case "ApplyEffect": + apa = new ApplyEffectPowerAction(rs, effects); + break; + case "ApplyEffects": + apa = new ApplyEffectsPowerAction(rs, effects); + break; + case "DeferredPower": + apa = new DeferredPowerPowerAction(rs, effects); + break; + case "DamageOverTime": + apa = new DamageOverTimePowerAction(rs, effects); + break; + case "Peek": + apa = new PeekPowerAction(rs); + break; + case "Charm": + apa = new CharmPowerAction(rs); + break; + case "Fear": + apa = new FearPowerAction(rs); + break; + case "Confusion": + apa = new ConfusionPowerAction(rs); + break; + case "RemoveEffect": + apa = new RemoveEffectPowerAction(rs); + break; + case "Track": + apa = new TrackPowerAction(rs, effects); + break; + case "DirectDamage": + apa = new DirectDamagePowerAction(rs, effects); + break; + case "Transform": + apa = new TransformPowerAction(rs, effects); + break; + case "CreateMob": + apa = new CreateMobPowerAction(rs); + break; + case "Invis": + apa = new InvisPowerAction(rs, effects); + break; + case "ClearNearbyAggro": + apa = new ClearNearbyAggroPowerAction(rs); + break; + case "MobRecall": + apa = new MobRecallPowerAction(rs); + break; + case "SetItemFlag": + apa = new SetItemFlagPowerAction(rs); + break; + case "SimpleDamage": + apa = new SimpleDamagePowerAction(rs); + break; + case "TransferStatOT": + apa = new TransferStatOTPowerAction(rs, effects); + break; + case "TransferStat": + apa = new TransferStatPowerAction(rs, effects); + break; + case "Teleport": + apa = new TeleportPowerAction(rs); + break; + case "TreeChoke": + apa = new TreeChokePowerAction(rs); + break; + case "Block": + apa = new BlockPowerAction(rs); + break; + case "Resurrect": + apa = new ResurrectPowerAction(rs); + break; + case "ClearAggro": + apa = new ClearAggroPowerAction(rs); + break; + case "ClaimMine": + apa = new ClaimMinePowerAction(rs); + break; + case "Recall": + apa = new RecallPowerAction(rs); + break; + case "SpireDisable": + apa = new SpireDisablePowerAction(rs); + break; + case "Steal": + apa = new StealPowerAction(rs); + break; + case "Summon": + apa = new SummonPowerAction(rs); + break; + case "RunegateTeleport": + apa = new RunegateTeleportPowerAction(rs); + break; + case "OpenGate": + apa = new OpenGatePowerAction(rs); + break; + } + powerActions.put(IDString, apa); + powerActionsByID.put(apa.UUID, apa); + apa.validItemFlags = 0; + } + rs.close(); + } catch (Exception e) { + Logger.error(e.toString()); + } finally { + ps.release(); + } + + + //Add OpenGatePowerAction + AbstractPowerAction openGateAction = new OpenGatePowerAction(5000, "OPENGATE", "OpenGate", false, 0); + powerActions.put("OPENGATE", openGateAction); + powerActionsByID.put(openGateAction.UUID, openGateAction); + } + + public static void loadValidItemFlags(HashMap powerActions) { + PreparedStatementShared ps = null; + try { + ps = new PreparedStatementShared("SELECT * FROM `static_power_effect_allowed_item`"); + ResultSet rs = ps.executeQuery(); + String IDS; + long flags; + while (rs.next()) { + AbstractPowerAction apa; + flags = rs.getLong("flags"); + IDS = rs.getString("IDString"); + if (powerActions.containsKey(IDS)) { + apa = powerActions.get(IDS); + apa.validItemFlags = flags; + } else { + Logger.error("Unable to find PowerAction " + IDS); + continue; + } + } + rs.close(); + } catch (Exception e) { + Logger.error(e.toString()); + } finally { + ps.release(); + } + + } + + public void startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb) { + this._startAction(source, awo, targetLoc, numTrains, ab, pb); + } + + public void startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb, int duration) { + this._startAction(source, awo, targetLoc, numTrains, ab, pb, duration); + } + + protected abstract void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb); + + protected abstract void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb, int duration); + + public void handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb) { + this._handleChant(source, target, targetLoc, numTrains, ab, pb); + } + + protected abstract void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb); + + public int getUUID() { + return this.UUID; + } + + // public String getMessageType() { + // return this.type; + // } + + public String getIDString() { + return this.IDString; + } + + public boolean isAggressive() { + return this.isAggressive; + } + + public PowersBase getParent() { + return this.parent; + } + + public void setParent(PowersBase value) { + this.parent = value; + } + + public void applyEffectForItem(Item item, int trains) { + if (this instanceof ApplyEffectPowerAction) + ((ApplyEffectPowerAction) this)._applyEffectForItem(item, trains); + if (this instanceof ApplyEffectsPowerAction) + ((ApplyEffectsPowerAction) this)._applyEffectForItem(item, trains); + } + + public void applyBakedInStatsForItem(Item item, int trains) { + if (this instanceof ApplyEffectPowerAction) + ((ApplyEffectPowerAction) this)._applyBakedInStatsForItem(item, trains); + if (this instanceof ApplyEffectsPowerAction) + ((ApplyEffectsPowerAction) this)._applyBakedInStatsForItem(item, trains); + } + + public EffectsBase getEffectsBase() { + if (this instanceof ApplyEffectPowerAction) + return ((ApplyEffectPowerAction) this).getEffect(); + else if (this instanceof ApplyEffectsPowerAction) + return ((ApplyEffectsPowerAction) this).getEffect(); + return null; + } + + public EffectsBase getEffectsBase2() { + if (this instanceof ApplyEffectsPowerAction) + return ((ApplyEffectsPowerAction) this).getEffect2(); + return null; + } + + //These functions verify a powerAction is valid for an item type + public long getValidItemFlags() { + return this.validItemFlags; + } + + public String getType() { + return type; + } + +} diff --git a/src/engine/powers/poweractions/ApplyEffectPowerAction.java b/src/engine/powers/poweractions/ApplyEffectPowerAction.java new file mode 100644 index 00000000..d96d3a54 --- /dev/null +++ b/src/engine/powers/poweractions/ApplyEffectPowerAction.java @@ -0,0 +1,259 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; +import engine.Enum.GameObjectType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.ChatManager; +import engine.jobs.ChantJob; +import engine.jobs.DeferredPowerJob; +import engine.jobs.FinishEffectTimeJob; +import engine.math.Vector3fImmutable; +import engine.net.DispatchMessage; +import engine.net.client.msg.chat.ChatSystemMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + +public class ApplyEffectPowerAction extends AbstractPowerAction { + + private String effectID; + private EffectsBase effect; + private String effectParentID; + private EffectsBase effectParent; + + public ApplyEffectPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + this.effectParentID = rs.getString("IDString"); + this.effectParent = effects.get(this.effectParentID); + this.effectID = rs.getString("effectID"); + this.effect = effects.get(this.effectID); + } + + public ApplyEffectPowerAction(ResultSet rs, EffectsBase effect) throws SQLException { + super(rs); + + this.effectID = rs.getString("effectID"); + this.effect = effect; + } + + private static boolean blockInvul(PowersBase pb, AbstractCharacter source, AbstractWorldObject awo) { + if (awo == null || pb == null || source == null) + return false; + + if (source.getObjectUUID() == awo.getObjectUUID()) + return false; + + if (!AbstractWorldObject.IsAbstractCharacter(awo)) + return false; + + AbstractCharacter ac = (AbstractCharacter) awo; + + + return false; + } + + public String getEffectID() { + return this.effectID; + } + + public EffectsBase getEffect() { + return this.effect; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + + //add schedule job to end it if needed and add effect to pc + int duration = 0; + // if (pb.isChant()) + // duration = (int)pb.getChantDuration() * 1000; + // else + duration = ab.getDuration(trains); + String stackType = ab.getStackType(); + if (stackType.equals("WeaponMove")) { + DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this); + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains); + else + awo.addEffect(stackType, 10000, eff, this.effect, trains); + if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) + ((PlayerCharacter) awo).setWeaponPower(eff); + this.effect.startEffect(source, awo, trains, eff); + } else { + FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect); + + if (blockInvul(pb, source, awo)) { + this.effect.endEffect(source, awo, trains, pb, eff); + return; + } + +// Effect lastEff = awo.getEffects().get(eff.getStackType()); +// +// if (lastEff != null && lastEff.getPowerToken() == eff.getPowerToken()) +// lastEff.cancelJob(true); + + if (duration > 0) { + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, effect, trains); + else + awo.addEffect(stackType, duration, eff, effect, trains); + } else + awo.applyAllBonuses(); + // //TODO if chant, start cycle + // if (pb.isChant() && source.equals(awo)) { + // ChantJob cj = new ChantJob(source, awo, stackType, trains, ab, pb, effect, eff); + // source.setLastChant((int)(pb.getChantDuration()-2) * 1000, cj); + // eff.setChant(true); + // } + + if (this.effectID.equals("TAUNT")) { + + if (awo != null && awo.getObjectType() == GameObjectType.Mob) { + ((Mob) awo).setCombatTarget(source); + ChatSystemMsg msg = ChatManager.CombatInfo(source, awo); + DispatchMessage.sendToAllInRange(source, msg); + ((Mob)awo).refresh(); + } + } + if (awo != null && awo.getObjectType() == GameObjectType.Mob) { + if(((Mob)awo).isPet()) { + ((Mob) awo).recalculateStats(); + } + } + this.effect.startEffect(source, awo, trains, eff); + } + + } + + protected void _applyEffectForItem(Item item, int trains) { + if (item == null || this.effect == null) + return; + item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains, false); + item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains, false); + } + + protected void _applyBakedInStatsForItem(Item item, int trains) { + if (item == null) + return; + if (this.effect == null) { + Logger.error("Unknown Token: EffectBase ID " + this.effectID + '.'); + return; + } + + if (this.effectParent == null) { + Logger.error("Unknown Token: EffectBase ID " + this.effectParentID + '.'); + return; + } + Effect eff = item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains, false); + Effect eff3 = item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains, false); + if (eff != null && eff3 != null) { + eff3.setBakedInStat(true); + item.getEffectNames().add(this.effect.getIDString()); + item.getEffectNames().add(this.effectParent.getIDString()); + } + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (source != null) { + PlayerBonuses bonuses = source.getBonuses(); + + if (bonuses == null) + return; + + boolean noSilence = bonuses.getBool(ModType.Silenced, SourceType.None); + + if (noSilence) + return; + + } + String stackType = ab.stackType; + stackType = stackType.equals("IgnoreStack") ? Integer.toString(ab.getUUID()) : stackType; + + FinishEffectTimeJob eff = new FinishEffectTimeJob(source, target, stackType, trains, ab, pb, effect); + ChantJob cj = new ChantJob(source, target, stackType, trains, ab, pb, effect, eff); + //handle invul + if (pb.getUUID() != 334) + source.setLastChant((int) (pb.getChantDuration()) * 1000, cj); + else + source.setLastChant((int) (pb.getChantDuration()) * 1000, cj); + eff.setChant(true); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int trains, ActionsBase ab, PowersBase pb, int duration) { + if (this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + + //add schedule job to end it if needed and add effect to pc + // if (pb.isChant()) + // duration = (int)pb.getChantDuration() * 1000; + // else + + String stackType = ab.getStackType(); + if (stackType.equals("WeaponMove")) { + DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this); + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains); + else + awo.addEffect(stackType, 10000, eff, this.effect, trains); + if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) + ((PlayerCharacter) awo).setWeaponPower(eff); + this.effect.startEffect(source, awo, trains, eff); + } else { + FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect); + + if (blockInvul(pb, source, awo)) { + this.effect.endEffect(source, awo, trains, pb, eff); + return; + } + + if (duration > 0) { + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, effect, trains); + else + awo.addEffect(stackType, duration, eff, effect, trains); + } else + awo.applyAllBonuses(); + // //TODO if chant, start cycle + // if (pb.isChant() && source.equals(awo)) { + // ChantJob cj = new ChantJob(source, awo, stackType, trains, ab, pb, effect, eff); + // source.setLastChant((int)(pb.getChantDuration()-2) * 1000, cj); + // eff.setChant(true); + // } + + if (this.effectID.equals("TAUNT")) { + + if (awo != null && awo.getObjectType() == GameObjectType.Mob) { + ((Mob) awo).setCombatTarget(source); + ChatSystemMsg msg = ChatManager.CombatInfo(source, awo); + DispatchMessage.sendToAllInRange(source, msg); + } + } + this.effect.startEffect(source, awo, trains, eff); + } + + } + +} diff --git a/src/engine/powers/poweractions/ApplyEffectsPowerAction.java b/src/engine/powers/poweractions/ApplyEffectsPowerAction.java new file mode 100644 index 00000000..2ef45305 --- /dev/null +++ b/src/engine/powers/poweractions/ApplyEffectsPowerAction.java @@ -0,0 +1,126 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Effect; +import engine.objects.Item; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class ApplyEffectsPowerAction extends AbstractPowerAction { + private String IDString; + private String effectID; + private String effectID2; + private EffectsBase effect; + private EffectsBase effect2; + private EffectsBase effectParent; + + public ApplyEffectsPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + this.IDString = rs.getString("IDString"); + this.effectID = rs.getString("effectID"); + this.effectID2 = rs.getString("effectID2"); + this.effect = effects.get(this.effectID); + this.effect2 = effects.get(this.effectID2); + this.effectParent = effects.get(this.IDString); + + } + + public String getEffectID() { + return this.effectID; + } + + public String getEffectID2() { + return this.effectID2; + } + + public EffectsBase getEffect() { + return this.effect; + } + + public EffectsBase getEffect2() { + return this.effect2; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + } + + protected void _applyEffectForItem(Item item, int trains) { + if (item == null || this.effect == null) + return; + item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains, false); + if (this.effect2 != null) + item.addEffectNoTimer(Integer.toString(this.effect2.getUUID()), this.effect2, trains, false); + item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains, false); + } + + protected void _applyBakedInStatsForItem(Item item, int trains) { + + if (item == null) + return; + + if (this.effect == null) { + Logger.error("Unknown Token: EffectBase ID " + this.effectID + '.'); + return; + } + + if (this.effect2 == null) { + Logger.error("Unknown Token: EffectBase ID " + this.effectID2 + '.'); + return; + } + + if (this.effectParent == null) { + Logger.error("Unknown Token: EffectBase ID " + this.IDString + '.'); + return; + } + + Effect eff = item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains, false); + Effect eff2 = item.addEffectNoTimer(Integer.toString(this.effect2.getUUID()), this.effect2, trains, false); + Effect eff3 = item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains, false); + + if (eff != null && eff2 != null && eff3 != null) { + eff3.setBakedInStat(true); + item.getEffectNames().add(this.effect.getIDString()); + item.getEffectNames().add(this.effect2.getIDString()); + item.getEffectNames().add(this.effectParent.getIDString()); + } + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + public String getIDString() { + return IDString; + } + + public void setIDString(String iDString) { + IDString = iDString; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/BlockPowerAction.java b/src/engine/powers/poweractions/BlockPowerAction.java new file mode 100644 index 00000000..0ae43341 --- /dev/null +++ b/src/engine/powers/poweractions/BlockPowerAction.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class BlockPowerAction extends AbstractPowerAction { + + public BlockPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/CharmPowerAction.java b/src/engine/powers/poweractions/CharmPowerAction.java new file mode 100644 index 00000000..100f5461 --- /dev/null +++ b/src/engine/powers/poweractions/CharmPowerAction.java @@ -0,0 +1,73 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.math.Vector3fImmutable; +import engine.net.client.ClientConnection; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class CharmPowerAction extends AbstractPowerAction { + + private int levelCap; + private int levelCapRamp; + + public CharmPowerAction(ResultSet rs) throws SQLException { + super(rs); + this.levelCap = rs.getInt("levelCap"); + this.levelCapRamp = rs.getInt("levelCapRamp"); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.Mob)) || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return; + + PlayerCharacter owner = (PlayerCharacter) source; + ClientConnection origin = owner.getClientConnection(); + + if (origin == null) + return; + + //verify is mob, not pet or guard + Mob mob = (Mob) awo; + if (!(mob.agentType.equals(Enum.AIAgentType.MOBILE))) + return; + + //make sure mob isn't too high level + int cap = this.levelCap + (this.levelCapRamp * trains); + if (mob.getLevel() > cap && pb.getToken() != 1577464266) + return; + + //turn mob into pet. + owner.commandSiegeMinion(mob); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/ClaimMinePowerAction.java b/src/engine/powers/poweractions/ClaimMinePowerAction.java new file mode 100644 index 00000000..ecb83284 --- /dev/null +++ b/src/engine/powers/poweractions/ClaimMinePowerAction.java @@ -0,0 +1,67 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.gameManager.ChatManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class ClaimMinePowerAction extends AbstractPowerAction { + + public ClaimMinePowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject worldObject, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (source == null || worldObject == null) + return; + + if (!(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return; + + PlayerCharacter playerCharacter = (PlayerCharacter) source; + + if (!(worldObject.getObjectType().equals(Enum.GameObjectType.Building))) + return; + + Building mineBuilding = (Building) worldObject; + + if (mineBuilding.getRank() > 0) + return; + + Mine mine = Mine.getMineFromTower(mineBuilding.getObjectUUID()); + + if (mine == null) + return; + + if (mine.claimMine(playerCharacter) == true) + ChatManager.sendSystemMessage((PlayerCharacter) source, "You successfully claimed this mine.."); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/ClearAggroPowerAction.java b/src/engine/powers/poweractions/ClearAggroPowerAction.java new file mode 100644 index 00000000..ea9c0f18 --- /dev/null +++ b/src/engine/powers/poweractions/ClearAggroPowerAction.java @@ -0,0 +1,49 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum.GameObjectType; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class ClearAggroPowerAction extends AbstractPowerAction { + + public ClearAggroPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (awo != null && awo.getObjectType() == GameObjectType.Mob) { + ((Mob) awo).setCombatTarget(null); + } + + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/ClearNearbyAggroPowerAction.java b/src/engine/powers/poweractions/ClearNearbyAggroPowerAction.java new file mode 100644 index 00000000..829fc0be --- /dev/null +++ b/src/engine/powers/poweractions/ClearNearbyAggroPowerAction.java @@ -0,0 +1,46 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum.GameObjectType; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class ClearNearbyAggroPowerAction extends AbstractPowerAction { + + public ClearNearbyAggroPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (source.getObjectType() == GameObjectType.Mob) { + } + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/ConfusionPowerAction.java b/src/engine/powers/poweractions/ConfusionPowerAction.java new file mode 100644 index 00000000..ca22c8eb --- /dev/null +++ b/src/engine/powers/poweractions/ConfusionPowerAction.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class ConfusionPowerAction extends AbstractPowerAction { + + public ConfusionPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/CreateMobPowerAction.java b/src/engine/powers/poweractions/CreateMobPowerAction.java new file mode 100644 index 00000000..90b64055 --- /dev/null +++ b/src/engine/powers/poweractions/CreateMobPowerAction.java @@ -0,0 +1,178 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.InterestManagement.WorldGrid; +import engine.gameManager.DbManager; +import engine.gameManager.MovementManager; +import engine.gameManager.NPCManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.PetMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class CreateMobPowerAction extends AbstractPowerAction { + + private int mobID; + private int mobLevel; + + public CreateMobPowerAction(ResultSet rs) throws SQLException { + super(rs); + + this.mobID = rs.getInt("mobID"); + this.mobLevel = rs.getInt("mobLevel"); + } + + public int getMobID() { + return this.mobID; + } + + public int getMobLevel() { + return this.mobLevel; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (source == null || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return; + + PlayerCharacter owner = (PlayerCharacter) source; + Mob currentPet = owner.getPet(); + Zone seaFloor = ZoneManager.getSeaFloor(); + Guild guild = Guild.getErrantGuild(); + ClientConnection origin = owner.getClientConnection(); + + if (seaFloor == null || guild == null || origin == null) + return; + + MobBase mobbase = MobBase.getMobBase(mobID); + + if (mobbase == null) { + Logger.error("Attempt to summon pet with null mobbase: " + mobID); + return; + } + + if (mobbase.isNecroPet() && owner.inSafeZone()) + return; + + //create Pet + Mob pet = Mob.createPet(mobID, guild, seaFloor, owner, (short) mobLevel); + + + if (pet.getMobBaseID() == 12021 || pet.getMobBaseID() == 12022) { //is a necro pet + if (currentPet != null && !currentPet.isNecroPet() && !currentPet.isSiege()) { + DbManager.removeFromCache(currentPet); + WorldGrid.RemoveWorldObject(currentPet); + currentPet.setCombatTarget(null); + + //if (currentPet.getParentZone() != null) + //currentPet.getParentZone().zoneMobSet.remove(currentPet); + seaFloor.zoneMobSet.remove(currentPet); + currentPet.playerAgroMap.clear(); + + try { + currentPet.clearEffects(); + } catch (Exception e) { + Logger.error(e.getMessage()); + } + + //currentPet.disableIntelligence(); + } else if (currentPet != null && currentPet.isSiege()) { + currentPet.agentType = Enum.AIAgentType.MOBILE; + currentPet.setOwner(null); + currentPet.setCombatTarget(null); + + if (currentPet.isAlive()) + WorldGrid.updateObject(currentPet); + } + //remove 10th pet + + + NPCManager.spawnNecroPet(owner, pet); + + } else { //is not a necro pet + if (currentPet != null) { + if (!currentPet.isNecroPet() && !currentPet.isSiege()) { + DbManager.removeFromCache(currentPet); + currentPet.setCombatTarget(null); + + currentPet.setOwner(null); + WorldGrid.RemoveWorldObject(currentPet); + //currentPet.getParentZone().zoneMobSet.remove(currentPet); + seaFloor.zoneMobSet.remove(currentPet); + currentPet.playerAgroMap.clear(); + currentPet.clearEffects(); + //currentPet.disableIntelligence(); + } else { + if (currentPet.isSiege()) { + currentPet.agentType = Enum.AIAgentType.MOBILE; + currentPet.setOwner(null); + currentPet.setCombatTarget(null); + + if (currentPet.isAlive()) + WorldGrid.updateObject(currentPet); + } + + } + NPCManager.auditNecroPets(owner); + NPCManager.resetNecroPets(owner); + } + } + /* if(owner.getPet() != null) { + if(owner.getPet().getMobBaseID() != 12021 && owner.getPet().getMobBaseID() != 12022) { + //if not a necro pet, remove pet + WorldGrid.removeWorldObject(owner.getPet()); + owner.getPet().disableIntelligence(); + Mob.removePet(owner.getPet().getUUID()); + owner.setPet(null); + } + else { + //if it is a necro pet, add it to the line and set as mob + owner.getPet().setMob(); + } + }*/ + + // if (mobID == 12021 || mobID == 12022) //Necro Pets + // pet.setPet(owner, true); + owner.setPet(pet); + if(pet.isSiege() == false) { + MovementManager.translocate(pet, owner.getLoc(), owner.region); + } + pet.recalculateStats(); + pet.healthMax = pet.level * 0.5f * 120; + pet.setHealth(pet.healthMax); + PetMsg pm = new PetMsg(5, pet); + Dispatch dispatch = Dispatch.borrow(owner, pm); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/DamageOverTimePowerAction.java b/src/engine/powers/poweractions/DamageOverTimePowerAction.java new file mode 100644 index 00000000..53d060d7 --- /dev/null +++ b/src/engine/powers/poweractions/DamageOverTimePowerAction.java @@ -0,0 +1,85 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.jobs.DamageOverTimeJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class DamageOverTimePowerAction extends AbstractPowerAction { + + private String effectID; + private int numIterations; + private EffectsBase effect; + + public DamageOverTimePowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + + this.effectID = rs.getString("effectID"); + this.numIterations = rs.getInt("numIterations"); + this.effect = effects.get(this.effectID); + } + + public String getEffectID() { + return this.effectID; + } + + public int getNumIterations() { + return this.numIterations; + } + + public EffectsBase getEffect() { + return this.effect; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + //add schedule job to end it if needed and add effect to pc + int duration = ab.getDuration(trains); + String stackType = ab.getStackType(); + DamageOverTimeJob eff = new DamageOverTimeJob(source, awo, stackType, trains, ab, pb, this.effect, this); + int tick = eff.getTickLength(); + if (duration > 0) { + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), eff.getTickLength(), eff, this.effect, trains); + else + awo.addEffect(stackType, tick, eff, this.effect, trains); + } + + //start effect icon for client. Skip applying dot until first iteration. + eff.setSkipApplyEffect(true); + this.effect.startEffect(source, awo, trains, eff); + eff.setSkipApplyEffect(false); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/DeferredPowerPowerAction.java b/src/engine/powers/poweractions/DeferredPowerPowerAction.java new file mode 100644 index 00000000..5d575f70 --- /dev/null +++ b/src/engine/powers/poweractions/DeferredPowerPowerAction.java @@ -0,0 +1,95 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.jobs.DeferredPowerJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class DeferredPowerPowerAction extends AbstractPowerAction { + + private String effectID; + private String deferedPowerID; + private EffectsBase effect; + // private EffectsBase deferedPower; + + public DeferredPowerPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + + this.effectID = rs.getString("effectID"); + this.deferedPowerID = rs.getString("deferredPowerID"); + this.effect = effects.get(this.effectID); + } + + public String getEffectID() { + return this.effectID; + } + + public String getDeferredPowerID() { + return this.deferedPowerID; + } + + public EffectsBase getEffect() { + return this.effect; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + + //add schedule job to end it if needed and add effect to pc + + String stackType = ab.getStackType(); + DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this); + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains); + else + awo.addEffect(stackType, 10000, eff, this.effect, trains); + + switch (awo.getObjectType()) { + case PlayerCharacter: + ((PlayerCharacter) awo).setWeaponPower(eff); + break; + case Mob: + ((Mob) awo).setWeaponPower(eff); + break; + default: + break; + } + + + this.effect.startEffect(source, awo, trains, eff); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/DirectDamagePowerAction.java b/src/engine/powers/poweractions/DirectDamagePowerAction.java new file mode 100644 index 00000000..4c670312 --- /dev/null +++ b/src/engine/powers/poweractions/DirectDamagePowerAction.java @@ -0,0 +1,93 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.jobs.FinishEffectTimeJob; +import engine.jobs.PersistentAoeJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class DirectDamagePowerAction extends AbstractPowerAction { + + private String effectID; + private EffectsBase effect; + + public DirectDamagePowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + + this.effectID = rs.getString("effectID"); + this.effect = effects.get(this.effectID); + } + + public String getEffectID() { + return this.effectID; + } + + public EffectsBase getEffect() { + return this.effect; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + + //add schedule job to end it if needed and add effect to pc + int duration = ab.getDuration(trains); + String stackType = ab.getStackType(); + FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, this.effect); + eff.setSkipSendEffect(true); + if (duration > 0) { + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, this.effect, trains); + else + awo.addEffect(stackType, duration, eff, this.effect, trains); + } + + // //if chant, start cycle + // if (pb.isChant() && targetLoc.x != 0f && targetLoc.z != 0f) { + // PersistentAoeJob paoe = new PersistentAoeJob(source, stackType, trains, ab, pb, effect, eff, targetLoc); + // source.addPersistantAoe(stackType, (int)(pb.getChantDuration() * 1000), paoe, effect, trains); + // eff.setChant(true); + // } + + this.effect.startEffect(source, awo, trains, eff); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + String stackType = ab.getStackType(); + stackType = stackType.equals("IgnoreStack") ? Integer.toString(ab.getUUID()) : stackType; + FinishEffectTimeJob eff = new FinishEffectTimeJob(source, target, stackType, trains, ab, pb, this.effect); + if (targetLoc.x != 0f && targetLoc.z != 0f) { + PersistentAoeJob paoe = new PersistentAoeJob(source, target, stackType, trains, ab, pb, effect, eff, targetLoc); + source.addPersistantAoe(stackType, (int) (pb.getChantDuration() * 1000), paoe, effect, trains); + eff.setChant(true); + } + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/FearPowerAction.java b/src/engine/powers/poweractions/FearPowerAction.java new file mode 100644 index 00000000..c1bae2ed --- /dev/null +++ b/src/engine/powers/poweractions/FearPowerAction.java @@ -0,0 +1,76 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.job.JobScheduler; +import engine.jobs.EndFearJob; +import engine.math.Vector3fImmutable; +import engine.net.client.ClientConnection; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class FearPowerAction extends AbstractPowerAction { + + private int levelCap; + private int levelCapRamp; + + public FearPowerAction(ResultSet rs) throws SQLException { + super(rs); + this.levelCap = rs.getInt("levelCap"); + this.levelCapRamp = rs.getInt("levelCapRamp"); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.Mob)) || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return; + + PlayerCharacter owner = (PlayerCharacter) source; + ClientConnection origin = owner.getClientConnection(); + if (origin == null) + return; + + //verify is mob, not pet or guard + Mob mob = (Mob) awo; + if (!(mob.agentType.equals(Enum.AIAgentType.MOBILE))) + return; + + //make sure mob isn't too high level + int cap = this.levelCap + (this.levelCapRamp * trains); + if (mob.getLevel() > cap || mob.getLevel() > 79) + return; + + //Apply fear to mob + int duration = 10 + ((int) (trains * 0.5)); + String stackType = ab.getStackType(); + EndFearJob efj = new EndFearJob(source, awo, stackType, trains, ab, pb, null); + ((Mob) awo).setFearedObject(source); + JobScheduler.getInstance().scheduleJob(efj, duration * 1000); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/InvisPowerAction.java b/src/engine/powers/poweractions/InvisPowerAction.java new file mode 100644 index 00000000..f213da91 --- /dev/null +++ b/src/engine/powers/poweractions/InvisPowerAction.java @@ -0,0 +1,78 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.jobs.FinishEffectTimeJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class InvisPowerAction extends AbstractPowerAction { + + private String effectID; + private EffectsBase effect; + + public InvisPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + + this.effectID = rs.getString("effectID"); + this.effect = effects.get(this.effectID); + } + + public String getEffectID() { + return this.effectID; + } + + public EffectsBase getEffect() { + return this.effect; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + + // if (this.effect.ignoreMod()) + // trains = 50; //set above see invis for safe mode and csr-invis + + //add schedule job to end it if needed and add effect to pc + int duration = ab.getDuration(trains); + String stackType = ab.getStackType(); + FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, this.effect); + if (duration > 0) { + if (stackType.equals("IgnoreStack")) + awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, this.effect, trains); + else + awo.addEffect(stackType, duration, eff, this.effect, trains); + } + this.effect.startEffect(source, awo, trains, eff); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/MobRecallPowerAction.java b/src/engine/powers/poweractions/MobRecallPowerAction.java new file mode 100644 index 00000000..d5318fcf --- /dev/null +++ b/src/engine/powers/poweractions/MobRecallPowerAction.java @@ -0,0 +1,60 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum.GameObjectType; +import engine.gameManager.MovementManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class MobRecallPowerAction extends AbstractPowerAction { + + public MobRecallPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (!AbstractWorldObject.IsAbstractCharacter(awo) || source == null) + return; + AbstractCharacter awoac = (AbstractCharacter) awo; + + if (awo.getObjectType() != GameObjectType.Mob) + return; + + + MovementManager.translocate(awoac, awoac.getBindLoc(), null); + if (awoac.getObjectType() == GameObjectType.Mob) { + //MobAI.setAwake((Mob)awoac,true); + ((Mob) awoac).setCombatTarget(null); + } + + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/OpenGatePowerAction.java b/src/engine/powers/poweractions/OpenGatePowerAction.java new file mode 100644 index 00000000..76ed8bfa --- /dev/null +++ b/src/engine/powers/poweractions/OpenGatePowerAction.java @@ -0,0 +1,124 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.GameObjectType; +import engine.Enum.PortalType; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.Runegate; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class OpenGatePowerAction extends AbstractPowerAction { + + /** + * ResultSet Constructor + */ + public OpenGatePowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + public OpenGatePowerAction(int uUID, String iDString, String type, boolean isAggressive, long validItemFlags) { + super(uUID, iDString, type, isAggressive, validItemFlags); + // TODO Auto-generated constructor stub + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + + if (awo.getObjectType().equals(GameObjectType.Building) == false) + return; + + Building targetBuilding = (Building) awo; + int token; + + // Sanity check. + + if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.Building)) || pb == null) + return; + + // Make sure building has a blueprint + + if (targetBuilding.getBlueprintUUID() == 0) + return; + + // Make sure building is actually a runegate. + + if (targetBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.RUNEGATE) + return; + + // Which portal was opened? + + token = pb.getToken(); + PortalType portalType = PortalType.AIR; + + switch (token) { + case 428937084: //Death Gate + portalType = PortalType.OBLIV; + break; + + case 429756284: //Chaos Gate + portalType = PortalType.CHAOS; + break; + + case 429723516: //Khar Gate + portalType = PortalType.MERCHANT; + break; + + case 429559676: //Spirit Gate + portalType = PortalType.SPIRIT; + break; + + case 429592444: //Water Gate + portalType = PortalType.WATER; + break; + + case 429428604: //Fire Gate + portalType = PortalType.FIRE; + break; + + case 429526908: //Air Gate + portalType = PortalType.AIR; + break; + + case 429625212: //Earth Gate + portalType = PortalType.EARTH; + break; + + default: + } + + // Which runegate was clicked on? + + Runegate runeGate = Runegate._runegates.get(targetBuilding.getObjectUUID()); + runeGate.activatePortal(portalType); + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/PeekPowerAction.java b/src/engine/powers/poweractions/PeekPowerAction.java new file mode 100644 index 00000000..824e58dd --- /dev/null +++ b/src/engine/powers/poweractions/PeekPowerAction.java @@ -0,0 +1,136 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.gameManager.ChatManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.LootWindowResponseMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; + + +public class PeekPowerAction extends AbstractPowerAction { + + public PeekPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + protected static boolean peekSuccess(AbstractCharacter pc, AbstractWorldObject awo) { + if (pc == null || awo == null || !AbstractWorldObject.IsAbstractCharacter(awo) || pc.getPowers() == null) + return false; + + int levelDif = pc.getLevel() - ((AbstractCharacter) awo).getLevel(); + + if (!pc.getPowers().containsKey(429494332)) + return false; + + CharacterPower cp = pc.getPowers().get(429494332); + int trains = cp.getTotalTrains(); + + float chance = 30 + (trains * 1.5f) + levelDif; + chance = (chance < 5f) ? 5f : chance; + chance = (chance > 95f) ? 95f : chance; + + float roll = ThreadLocalRandom.current().nextFloat() * 100f; + + return roll < chance; + + } + + protected static boolean peekDetect(AbstractCharacter pc, AbstractWorldObject awo) { + if (pc == null || awo == null || !AbstractWorldObject.IsAbstractCharacter(awo) || pc.getPowers() == null) + return false; + + int levelDif = pc.getLevel() - ((AbstractCharacter) awo).getLevel(); + + if (!pc.getPowers().containsKey(429494332)) + return false; + + CharacterPower cp = pc.getPowers().get(429494332); + int trains = cp.getTotalTrains(); + + // check if peek is detected + float chance = 30 + (40 - trains) * 1.5f - levelDif; + chance = (chance < 5f) ? 5f : chance; + chance = (chance > 95f) ? 95f : chance; + + float roll = ThreadLocalRandom.current().nextFloat() * 100f; + return roll < chance; + + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (source == null || awo == null || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return; + + PlayerCharacter pc = null; + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) + pc = (PlayerCharacter) source; + + AbstractCharacter target = null; + if (AbstractWorldObject.IsAbstractCharacter(awo)) + target = (AbstractCharacter) awo; + + //test probability of successful peek + boolean peekSuccess = peekSuccess(source, awo); + if (peekSuccess) { + ChatManager.chatPeekSteal(pc, target, null, true, peekDetect(source, awo), -1); + } else { + ChatManager.chatPeekSteal(pc, target, null, false, false, -1); + return; + } + + LootWindowResponseMsg lwrm = null; + + if (awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + + PlayerCharacter tar = (PlayerCharacter) awo; + + if (!tar.isAlive()) + return; + + lwrm = new LootWindowResponseMsg(tar.getObjectType().ordinal(), tar.getObjectUUID(), tar.getInventory(true)); + } else if (awo.getObjectType().equals(Enum.GameObjectType.Mob)) { + + Mob tar = (Mob) awo; + + if (!tar.isAlive()) + return; + + lwrm = new LootWindowResponseMsg(tar.getObjectType().ordinal(), tar.getObjectUUID(), tar.getInventory(true)); + } + if (lwrm == null) + return; + + Dispatch dispatch = Dispatch.borrow(pc, lwrm); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/RecallPowerAction.java b/src/engine/powers/poweractions/RecallPowerAction.java new file mode 100644 index 00000000..c0ffb409 --- /dev/null +++ b/src/engine/powers/poweractions/RecallPowerAction.java @@ -0,0 +1,87 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum.GameObjectType; +import engine.InterestManagement.WorldGrid; +import engine.gameManager.MovementManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.PromptRecallMsg; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Mob; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class RecallPowerAction extends AbstractPowerAction { + + public RecallPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (!AbstractWorldObject.IsAbstractCharacter(awo) || source == null) + return; + AbstractCharacter awoac = (AbstractCharacter) awo; + + if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { + + PlayerCharacter pc = (PlayerCharacter) awo; + + if (pc.hasBoon()) + return; + + ClientConnection cc = pc.getClientConnection(); + + if (source.getObjectUUID() != pc.getObjectUUID()) { + pc.setTimeStampNow("PromptRecall"); + pc.setTimeStamp("LastRecallType", 1); //recall to bind + PromptRecallMsg promptRecallMsgmsg = new PromptRecallMsg(); + Dispatch dispatch = Dispatch.borrow(pc, promptRecallMsgmsg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + } else { + MovementManager.translocate(awoac, awoac.getBindLoc(), null); + } + } else { + Vector3fImmutable bindloc = awoac.getBindLoc(); + if (bindloc.x == 0.0f || bindloc.y == 0.0f) + awoac.setBindLoc(MBServerStatics.startX, MBServerStatics.startY, MBServerStatics.startZ); + awoac.teleport(awoac.getBindLoc()); + if (awoac.getObjectType() == GameObjectType.Mob) { + ((Mob) awoac).setCombatTarget(null); + if (awoac.isAlive()) + WorldGrid.updateObject(awoac); + } + + } + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/RemoveEffectPowerAction.java b/src/engine/powers/poweractions/RemoveEffectPowerAction.java new file mode 100644 index 00000000..0cb838de --- /dev/null +++ b/src/engine/powers/poweractions/RemoveEffectPowerAction.java @@ -0,0 +1,61 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum.EffectSourceType; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class RemoveEffectPowerAction extends AbstractPowerAction { + + + public EffectSourceType sourceType; + private boolean removeAll; + + public RemoveEffectPowerAction(ResultSet rs) throws SQLException { + super(rs); + String effectTypeToRemove = rs.getString("effectSourceToRemove").replace("-", "").trim(); + sourceType = EffectSourceType.GetEffectSourceType(effectTypeToRemove); + int flags = rs.getInt("flags"); + this.removeAll = ((flags & 2) != 0) ? true : false; + } + + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (awo != null) { + + + if (this.removeAll) + awo.removeEffectBySource(this.sourceType, trains, true); + else if (this.getIDString().equals("SNC-003A")) + trains = 40; + awo.removeEffectBySource(this.sourceType, trains, false); + } + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/ResurrectPowerAction.java b/src/engine/powers/poweractions/ResurrectPowerAction.java new file mode 100644 index 00000000..b21e8de8 --- /dev/null +++ b/src/engine/powers/poweractions/ResurrectPowerAction.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class ResurrectPowerAction extends AbstractPowerAction { + + public ResurrectPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/RunegateTeleportPowerAction.java b/src/engine/powers/poweractions/RunegateTeleportPowerAction.java new file mode 100644 index 00000000..09416a86 --- /dev/null +++ b/src/engine/powers/poweractions/RunegateTeleportPowerAction.java @@ -0,0 +1,87 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.msg.PromptRecallMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import static engine.math.FastMath.sqr; +import static engine.math.FastMath.sqrt; + +public class RunegateTeleportPowerAction extends AbstractPowerAction { + + /** + * ResultSet Constructor + */ + public RunegateTeleportPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return; + + PlayerCharacter pc = (PlayerCharacter) awo; + float dist = 9999999999f; + Building rg = null; + Vector3fImmutable rgLoc; + + for (Runegate runegate : Runegate._runegates.values()) { + + rgLoc = runegate.gateBuilding.getLoc(); + + float distanceToRunegateSquared = source.getLoc().distanceSquared2D(rgLoc); + + if (distanceToRunegateSquared < sqr(dist)) { + dist = sqrt(distanceToRunegateSquared); + rg = runegate.gateBuilding; + } + } + + if (source.getObjectUUID() != pc.getObjectUUID()) { + pc.setTimeStampNow("PromptRecall"); + pc.setTimeStamp("LastRecallType", 0); //recall to rg + + if (rg != null) { + PromptRecallMsg promptRecallMsgmsg = new PromptRecallMsg(); + Dispatch dispatch = Dispatch.borrow(pc, promptRecallMsgmsg); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + } + + } else { + if (rg != null) { + pc.teleport(rg.getLoc()); + pc.setSafeMode(); + } + } + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/SetItemFlagPowerAction.java b/src/engine/powers/poweractions/SetItemFlagPowerAction.java new file mode 100644 index 00000000..3bd976a0 --- /dev/null +++ b/src/engine/powers/poweractions/SetItemFlagPowerAction.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.CharacterItemManager; +import engine.objects.Item; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class SetItemFlagPowerAction extends AbstractPowerAction { + + public SetItemFlagPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.Item))) + return; + + Item item = (Item) awo; + + if (item.containerType != Enum.ItemContainerType.INVENTORY) + return; //Send an error here? + + //until this is shown to do something else, just use it as item identify spell. + item.setIsID(true); + + if (!DbManager.ItemQueries.UPDATE_FLAGS(item)) + item.setIsID(false); //update failed, reset + + //update inventory + CharacterItemManager cim = source.getCharItemManager(); + if (cim != null) + cim.updateInventory(); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/SimpleDamagePowerAction.java b/src/engine/powers/poweractions/SimpleDamagePowerAction.java new file mode 100644 index 00000000..1c16c146 --- /dev/null +++ b/src/engine/powers/poweractions/SimpleDamagePowerAction.java @@ -0,0 +1,51 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class SimpleDamagePowerAction extends AbstractPowerAction { + + private int simpleDamage; + + public SimpleDamagePowerAction(ResultSet rs) throws SQLException { + super(rs); + + this.simpleDamage = rs.getInt("simpleDamage"); + } + + public int getSimpleDamage() { + return this.simpleDamage; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/SpireDisablePowerAction.java b/src/engine/powers/poweractions/SpireDisablePowerAction.java new file mode 100644 index 00000000..b61c0166 --- /dev/null +++ b/src/engine/powers/poweractions/SpireDisablePowerAction.java @@ -0,0 +1,88 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum.BuildingGroup; +import engine.Enum.GameObjectType; +import engine.gameManager.ChatManager; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.Building; +import engine.objects.PlayerCharacter; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SpireDisablePowerAction extends AbstractPowerAction { + + /** + * ResultSet Constructor + */ + public SpireDisablePowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (awo == null) + return; + + if (source == null) + return; + + PlayerCharacter pc = null; + + if (source.getObjectType() == GameObjectType.PlayerCharacter) + pc = (PlayerCharacter) source; + else + return; + + if (awo.getObjectType() != GameObjectType.Building) + return; + + + //Check if Building is Spire. + + Building spire = (Building) awo; + + if ((spire.getBlueprintUUID() == 0) || + (spire.getBlueprint() != null && spire.getBlueprint().getBuildingGroup() != BuildingGroup.SPIRE)) { + ChatManager.chatSystemError((PlayerCharacter) source, "This Building is not a spire."); + return; + } + + if (!spire.isSpireIsActive()) + return; + + spire.disableSpire(false); + + if (trains > 20) + trains = 20; + + long duration = trains * 4500 + 30000; + spire.setTimeStamp("DISABLED", System.currentTimeMillis() + duration); + + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/StealPowerAction.java b/src/engine/powers/poweractions/StealPowerAction.java new file mode 100644 index 00000000..545137d1 --- /dev/null +++ b/src/engine/powers/poweractions/StealPowerAction.java @@ -0,0 +1,199 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.Enum.ItemType; +import engine.gameManager.ChatManager; +import engine.gameManager.CombatManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.LootMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; + +import static engine.math.FastMath.sqr; + + +public class StealPowerAction extends AbstractPowerAction { + + /** + * ResultSet Constructor + */ + public StealPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + protected static boolean stealSuccess(PlayerCharacter pc, AbstractWorldObject awo) { + if (pc == null || awo == null || !AbstractWorldObject.IsAbstractCharacter(awo) || pc.getPowers() == null) + return false; + + int levelDif = pc.getLevel() - ((AbstractCharacter) awo).getLevel(); + + if (!pc.getPowers().containsKey(429396028)) + return false; + + CharacterPower cp = pc.getPowers().get(429396028); + int trains = cp.getTotalTrains(); + + float chance = 20 + (trains * 1.5f) + levelDif; + chance = (chance < 5f) ? 5f : chance; + chance = (chance > 85f) ? 85f : chance; + + float roll = ThreadLocalRandom.current().nextFloat() * 100f; + + return roll < chance; + + } + + //called to get amount of gold to steal between 0 and max gold + protected static int getAmountToSteal(Item i) { + if (i.getItemBase() != null && i.getItemBase().getUUID() == 7) { + int amount = i.getNumOfItems(); + if (amount < 1) + return -1; + int a = ThreadLocalRandom.current().nextInt(amount + 1); + int b = ThreadLocalRandom.current().nextInt(amount + 1); + int c = ThreadLocalRandom.current().nextInt(amount + 1); + return (a + b + c) / 3; + } else + return 0; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (source == null || awo == null || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) || !(awo.getObjectType().equals(Enum.GameObjectType.Item))) + return; + + PlayerCharacter sourcePlayer = (PlayerCharacter) source; + + if (sourcePlayer.isSafeMode()) + return; + + if (!sourcePlayer.isAlive()) + return; + + //prevent stealing no steal mob loot + if (awo instanceof MobLoot && ((MobLoot) awo).noSteal()) + return; + + Item tar = (Item) awo; + AbstractWorldObject owner = (AbstractWorldObject) tar.getOwner(); + + if (owner == null) + return; + + + AbstractCharacter ownerAC = null; + if (AbstractWorldObject.IsAbstractCharacter(owner)) + ownerAC = (AbstractCharacter) owner; + + if (ownerAC != null) + if (ownerAC.getLoc().distanceSquared(sourcePlayer.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) + return; + + //only steal from players or mobs + + if (owner.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + + PlayerCharacter ownerPC = (PlayerCharacter) owner; + + if (ownerPC.isSafeMode() || sourcePlayer.inSafeZone() || ownerPC.inSafeZone()) + return; + + if (ownerPC.getLoc().distanceSquared(sourcePlayer.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) + return; + + //dupe check, validate player has item + if (!tar.validForInventory(ownerPC.getClientConnection(), ownerPC, ownerPC.getCharItemManager()))//pc.getCharItemManager())) + return; + + //mark thief and target as player aggressive + sourcePlayer.setLastPlayerAttackTime(); + ownerPC.setLastPlayerAttackTime(); + + //Handle target attacking back if in combat and has no other target + CombatManager.handleRetaliate(ownerAC, sourcePlayer); + + } else + return; + + ClientConnection origin = sourcePlayer.getClientConnection(); + + if (origin == null) + return; + + int amount = getAmountToSteal(tar); + + //test probability of steal success + if (!stealSuccess(sourcePlayer, owner)) { + ChatManager.chatPeekSteal(sourcePlayer, ownerAC, tar, false, false, -1); + return; + } else { + ChatManager.chatPeekSteal(sourcePlayer, ownerAC, tar, true, false, amount); + //TODO send steal failure success + } + + //attempt transfer item + CharacterItemManager myCIM = sourcePlayer.getCharItemManager(); + CharacterItemManager ownerCIM = ((AbstractCharacter) owner).getCharItemManager(); + if (myCIM == null || ownerCIM == null) + return; + + if (tar.getItemBase().getType().equals(ItemType.GOLD)) { + //stealing gold + if (!myCIM.transferGoldToMyInventory((AbstractCharacter) owner, amount)) + return; + } else { + //stealing items + if (ownerCIM.lootItemFromMe(tar, sourcePlayer, origin, true, amount) == null) + return; + } + + //send loot message to person stealing. + LootMsg lm = new LootMsg(source.getObjectType().ordinal(), source.getObjectUUID(), owner.getObjectType().ordinal(), owner.getObjectUUID(), tar); + Dispatch dispatch = Dispatch.borrow(sourcePlayer, lm); + DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY); + + //update thief's inventory + if (sourcePlayer.getCharItemManager() != null) + sourcePlayer.getCharItemManager().updateInventory(); + + //update victims inventory + if (owner.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter ownerPC = (PlayerCharacter) owner; + + if (ownerPC.getCharItemManager() != null) + ownerPC.getCharItemManager().updateInventory(); + } + + //TODO if victim is trading, cancel trade window for both people involved in trade + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/SummonPowerAction.java b/src/engine/powers/poweractions/SummonPowerAction.java new file mode 100644 index 00000000..92d3c92e --- /dev/null +++ b/src/engine/powers/poweractions/SummonPowerAction.java @@ -0,0 +1,78 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.gameManager.SessionManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.client.ClientConnection; +import engine.net.client.msg.RecvSummonsRequestMsg; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.objects.PlayerCharacter; +import engine.objects.Zone; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SummonPowerAction extends AbstractPowerAction { + + /** + * ResultSet Constructor + */ + public SummonPowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, + PowersBase pb) { + + if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))) + return; + + PlayerCharacter target = (PlayerCharacter) awo; + + ClientConnection conn = SessionManager.getClientConnection(target); + + if (conn == null) + return; + + // TODO get location of summoning player + Zone zone = ZoneManager.findSmallestZone(source.getLoc()); + String location = "Somewhere"; + + if (zone != null) + location = zone.getName(); + + RecvSummonsRequestMsg rsrm = new RecvSummonsRequestMsg(source.getObjectType().ordinal(), source.getObjectUUID(), source.getFirstName(), + location, false); + + Dispatch dispatch = Dispatch.borrow(target, rsrm); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/TeleportPowerAction.java b/src/engine/powers/poweractions/TeleportPowerAction.java new file mode 100644 index 00000000..36eed471 --- /dev/null +++ b/src/engine/powers/poweractions/TeleportPowerAction.java @@ -0,0 +1,116 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.MovementManager; +import engine.gameManager.PowersManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; +import engine.powers.effectmodifiers.AbstractEffectModifier; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class TeleportPowerAction extends AbstractPowerAction { + + private boolean ignoreNoTeleSpire; + + public TeleportPowerAction(ResultSet rs) throws SQLException { + super(rs); + + int flags = rs.getInt("flags"); + this.ignoreNoTeleSpire = ((flags & 32768) != 0) ? true : false; + } + + private static void failTeleport(PowersBase pb, AbstractCharacter awo) { + + if (pb == null || awo == null || (!(awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))) + return; + + //teleport failed. Reset teleport power + PowersManager.finishRecycleTime(pb.getToken(), (PlayerCharacter) awo, true); + } + + public boolean ignoreNoTeleSpire() { + return this.ignoreNoTeleSpire; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + if (!AbstractWorldObject.IsAbstractCharacter(awo)) + return; + + AbstractCharacter awoac = (AbstractCharacter) awo; + + //verify targetLoc within range + + if (awo.getLoc().distanceSquared2D(targetLoc) > MBServerStatics.MAX_TELEPORT_RANGE * MBServerStatics.MAX_TELEPORT_RANGE) { + if (awo.equals(source)) + failTeleport(pb, awoac); + return; + } + + if (source.getBonuses().getBool(ModType.BlockedPowerType, SourceType.TELEPORT)) + return; + + City city = ZoneManager.getCityAtLocation(targetLoc); + + // Intentionally fail if target location is not on + // the actual city zone. + if (city != null) + if (city.isLocationOnCityZone(targetLoc) == false) + city = null; + + if (city != null) { + + for (String eff : city.getEffects().keySet()) { + + Effect spireEffect = city.getEffects().get(eff); + + for (AbstractEffectModifier aem : spireEffect.getEffectModifiers()) { + + if (aem.getType().equals("TELEPORT") && !this.ignoreNoTeleSpire) { + if (awo.equals(source)) + failTeleport(pb, awoac); + return; + } + } + } + } + + //TODO verify target loc is valid loc + + Regions region = Regions.GetRegionForTeleport(targetLoc); + + if (region != null && !region.isOutside()) + return; + + MovementManager.translocate(awoac, targetLoc, region); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + } +} \ No newline at end of file diff --git a/src/engine/powers/poweractions/TrackPowerAction.java b/src/engine/powers/poweractions/TrackPowerAction.java new file mode 100644 index 00000000..c9d4fdee --- /dev/null +++ b/src/engine/powers/poweractions/TrackPowerAction.java @@ -0,0 +1,127 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.jobs.TrackJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class TrackPowerAction extends AbstractPowerAction { + + private String effectID; + private boolean trackPlayer; + private boolean trackCorpse; + private boolean trackAll; + private boolean trackDragon; + private boolean trackGiant; + private boolean trackNPC; + private boolean trackUndead; + private boolean trackVampire; + private int maxTrack; + private EffectsBase effect; + + public TrackPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + + this.effectID = rs.getString("effectID"); + int flags = rs.getInt("flags"); + this.trackPlayer = ((flags & 1024) == 1) ? true : false; + this.trackCorpse = ((flags & 2048) == 1) ? true : false; + String trackFilter = rs.getString("trackFilter"); + this.trackAll = trackFilter.equals("All") ? true : false; + this.trackDragon = trackFilter.equals("Dragon") ? true : false; + this.trackGiant = trackFilter.equals("Giant") ? true : false; + this.trackNPC = trackFilter.equals("NPC") ? true : false; + this.trackUndead = trackFilter.equals("Undead") ? true : false; + this.trackVampire = trackFilter.equals("Vampire") ? true : false; + + this.maxTrack = rs.getInt("maxTrack"); + this.effect = effects.get(this.effectID); + } + + public String getEffectID() { + return this.effectID; + } + + public boolean trackPlayer() { + return this.trackPlayer; + } + + public boolean trackCorpse() { + return this.trackCorpse; + } + + public boolean trackAll() { + return this.trackAll; + } + + public boolean trackDragon() { + return this.trackDragon; + } + + public boolean trackGiant() { + return this.trackGiant; + } + + public boolean trackNPC() { + return this.trackNPC; + } + + public boolean trackUndead() { + return this.trackUndead; + } + + public boolean trackVampire() { + return this.trackVampire; + } + + public int getMaxTrack() { + return this.maxTrack; + } + + public EffectsBase getEffect() { + return this.effect; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (source == null || awo == null || this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + + //add schedule job to end it if needed and add effect to pc + int duration = MBServerStatics.TRACK_ARROW_SENSITIVITY; + String stackType = ab.getStackType(); + TrackJob eff = new TrackJob(source, awo, stackType, trains, ab, pb, this.effect, this); + source.addEffect(stackType, duration, eff, this.effect, trains); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/TransferStatOTPowerAction.java b/src/engine/powers/poweractions/TransferStatOTPowerAction.java new file mode 100644 index 00000000..a88a7237 --- /dev/null +++ b/src/engine/powers/poweractions/TransferStatOTPowerAction.java @@ -0,0 +1,68 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.jobs.TransferStatOTJob; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class TransferStatOTPowerAction extends TransferStatPowerAction { + + private int numIterations; + + public TransferStatOTPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs, effects); + + this.numIterations = rs.getInt("numIterations"); + } + + public int getNumIterations() { + return this.numIterations; + } + + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3f targetLoc, int trains, ActionsBase ab, PowersBase pb) { + this.__startAction(source, awo, trains, ab, pb); + } + + @Override + protected void __startAction(AbstractCharacter source, AbstractWorldObject awo, int trains, ActionsBase ab, PowersBase pb) { + if (this.effect == null || source == null || awo == null || ab == null || pb == null) + return; + + //add schedule job to end it if needed and add effect to pc + int duration = ab.getDuration(trains); + String stackType = ab.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; + TransferStatOTJob eff = new TransferStatOTJob(source, awo, stackType, trains, ab, pb, this.effect, this); + int tick = eff.getTickLength(); + + if (duration > 0) + awo.addEffect(stackType, tick, eff, this.effect, trains); + + //start effect icon for client. Skip applying dot until first iteration. + eff.setSkipApplyEffect(true); + this.effect.startEffect(source, awo, trains, eff); + eff.setSkipApplyEffect(false); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } +} diff --git a/src/engine/powers/poweractions/TransferStatPowerAction.java b/src/engine/powers/poweractions/TransferStatPowerAction.java new file mode 100644 index 00000000..4deea480 --- /dev/null +++ b/src/engine/powers/poweractions/TransferStatPowerAction.java @@ -0,0 +1,298 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.Enum; +import engine.Enum.DamageType; +import engine.Enum.ModType; +import engine.Enum.SourceType; +import engine.gameManager.ChatManager; +import engine.math.Vector3fImmutable; +import engine.net.AbstractNetMsg; +import engine.net.DispatchMessage; +import engine.net.client.msg.ModifyHealthKillMsg; +import engine.net.client.msg.ModifyHealthMsg; +import engine.objects.*; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; +import engine.powers.effectmodifiers.HealthEffectModifier; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class TransferStatPowerAction extends AbstractPowerAction { + + protected String effectID; + protected boolean transferFromHealth = false; + protected boolean transferFromMana = false; + protected boolean transferFromStamina = false; + protected boolean transferToHealth = false; + protected boolean transferToMana = false; + protected boolean transferToStamina = false; + protected float transferAmount; + protected float transferRamp; + protected boolean transferRampAdd; + protected float transferEfficiency; + protected float transferEfficiencyRamp; + protected boolean transferEfficiencyRampAdd; + protected boolean targetToCaster; + protected DamageType damageType; + protected EffectsBase effect; + + public TransferStatPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + this.effectID = rs.getString("effectID"); + String st = rs.getString("transferFromType"); + if (st.equals("HEALTH")) + this.transferFromHealth = true; + else if (st.equals("MANA")) + this.transferFromMana = true; + else + this.transferFromStamina = true; + st = rs.getString("transferToType"); + if (st.equals("HEALTH")) + this.transferToHealth = true; + else if (st.equals("MANA")) + this.transferToMana = true; + else + this.transferToStamina = true; + this.transferAmount = rs.getFloat("transferAmount"); + this.transferRamp = rs.getFloat("transferRamp"); + this.transferEfficiency = rs.getFloat("transferEfficiency"); + this.transferEfficiencyRamp = rs.getFloat("transferEfficiencyRamp"); + int flags = rs.getInt("flags"); + this.transferRampAdd = ((flags & 4096) != 0) ? true : false; + this.transferEfficiencyRampAdd = ((flags & 8192) != 0) ? true : false; + this.targetToCaster = ((flags & 16384) != 0) ? true : false; + this.effect = effects.get(this.effectID); + try { + String damageString = rs.getString("damageType"); + // Damage type can sometimes be null in the DB. + + if (damageString.isEmpty() == false) + this.damageType = DamageType.valueOf(damageString); + } catch (Exception e) { + this.damageType = null; + } + } + + public String getEffectID() { + return this.effectID; + } + + public boolean transferFromHealth() { + return this.transferFromHealth; + } + + public boolean transferFromMana() { + return this.transferFromMana; + } + + public boolean transferFromStamina() { + return this.transferFromStamina; + } + + public boolean transferToHealth() { + return this.transferToHealth; + } + + public boolean transferToMana() { + return this.transferToMana; + } + + public boolean transferToStamina() { + return this.transferToStamina; + } + + public EffectsBase getEffect() { + return this.effect; + } + + public float getTransferAmount(float trains) { + // if (this.transferRampAdd) + return this.transferAmount + (this.transferRamp * trains); + // else + // return this.transferAmount * (1 + (this.transferRamp * trains)); + } + + public float getTransferEfficiency(float trains) { + return this.transferEfficiency + (this.transferEfficiencyRamp * trains); + } + + public boolean targetToCaster() { + return this.targetToCaster; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + this.__startAction(source, awo, trains, ab, pb); + } + + //Added for dependancy check on TransferStatOTPowerAction + protected void __startAction(AbstractCharacter source, AbstractWorldObject awo, int trains, ActionsBase ab, PowersBase pb) { + this.runAction(source, awo, trains, ab, pb); + } + + public void runAction(AbstractCharacter source, AbstractWorldObject awo, int trains, ActionsBase ab, PowersBase pb) { + if (source == null || awo == null || ab == null || pb == null) + return; + + if (!source.isAlive() || !awo.isAlive()) + return; + + AbstractWorldObject fromAwo; + AbstractWorldObject toAwo; + if (this.targetToCaster) { + fromAwo = awo; + toAwo = source; + } else { + fromAwo = source; + toAwo = awo; + } + + + if (AbstractWorldObject.IsAbstractCharacter(fromAwo) && AbstractWorldObject.IsAbstractCharacter(toAwo)) { + AbstractCharacter from = (AbstractCharacter) fromAwo; + AbstractCharacter to = (AbstractCharacter) toAwo; + + //get amount to drain + float fromAmount = getTransferAmount(trains); + + //modify for resists if needed + if (this.damageType != null) { + Resists resists = from.getResists(); + if (resists != null) + fromAmount = resists.getResistedDamage(to, from, this.damageType, fromAmount * -1, trains) * -1; + } + + float min = fromAmount;// * (getTransferEfficiency(trains) / 100); + float max = min; + float damage = 0f; + + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + float focus; + CharacterSkill skill = pc.getSkills().get(pb.getSkillName()); + if (skill == null) + focus = CharacterSkill.getQuickMastery(pc, pb.getSkillName()); + else + focus = skill.getModifiedAmount(); + + //TODO fix this formula later + float intt = (pc.getStatIntCurrent() >= 1) ? (float) pc.getStatIntCurrent() : 1f; + float spi = (pc.getStatSpiCurrent() >= 1) ? (float) pc.getStatSpiCurrent() : 1f; + // min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus); + // max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus); + // min *= (0.62 + 0.0192 * pc.getStatSpiCurrent() + 0.00415 * pc.getStatIntCurrent() + 0.015 * focus) / 2; + // max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus) / 2; + min = HealthEffectModifier.getMinDamage(min, intt, spi, focus); + max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus); + + // get range between min and max + float range = max - min; + + //debug for spell damage and atr + if (pc.getDebug(16)) { + String smsg = "Damage: " + (int) Math.abs(min) + " - " + (int) Math.abs(max); + ChatManager.chatSystemInfo(pc, smsg); + } + + // Damage is calculated twice to average a more central point + damage = ThreadLocalRandom.current().nextFloat() * range; + damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2; + + // put it back between min and max + damage += min; + } + + // Apply any power effect modifiers (such as stances) + PlayerBonuses bonus = source.getBonuses(); + if (bonus != null) + damage *= (1 + bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None)); + + //get amount to transfer + fromAmount = damage; + float toAmount = fromAmount * (getTransferEfficiency(trains) / 100); + + //get max amount to transfer, don't give more then the target has + float maxDrain; + if (this.transferFromHealth) + maxDrain = from.getCurrentHitpoints(); + else if (this.transferFromMana) + maxDrain = from.getMana(); + else + maxDrain = from.getStamina(); + if (toAmount > maxDrain) + toAmount = maxDrain; + + //prep messages for transfer + int powerID = pb.getToken(); + int effectID = 496519310; + String powerName = pb.getName(); + ModifyHealthMsg mhmTo; + // ModifyHealthMsg mhmFrom; + AbstractNetMsg mhmFrom = null; + + //stop if target is immune to drains + if (from.getBonuses().getBool(ModType.ImmuneTo, SourceType.Drain)) { + ModifyHealthMsg mhm = new ModifyHealthMsg(source, to, 0f, 0f, 0f, powerID, powerName, trains, effectID); + mhm.setUnknown03(5); //set target is immune + DispatchMessage.sendToAllInRange(from, mhm); + return; + } + + //apply transfer bonus + if (this.transferToHealth) { + to.modifyHealth(toAmount, source, false); + mhmTo = new ModifyHealthMsg(source, to, toAmount, 0f, 0f, powerID, powerName, trains, effectID); + } else if (this.transferToMana) { + to.modifyMana(toAmount, source); + mhmTo = new ModifyHealthMsg(source, to, 0f, toAmount, 0f, powerID, powerName, trains, effectID); + } else { + to.modifyStamina(toAmount, source); + mhmTo = new ModifyHealthMsg(source, to, 0f, 0f, toAmount, powerID, powerName, trains, effectID); + } + + //subtract transfer amount + if (this.transferFromHealth) { + float modFrom = from.modifyHealth(-fromAmount, source, false); + float cur = from.getHealth(); + if (cur < 0 && modFrom != 0) + mhmFrom = new ModifyHealthKillMsg(source, from, -fromAmount, 0f, 0f, powerID, powerName, trains, effectID); + else + mhmFrom = new ModifyHealthMsg(source, from, -fromAmount, 0f, 0f, powerID, powerName, trains, effectID); + } else if (this.transferFromMana) { + from.modifyMana(-fromAmount, source); + mhmFrom = new ModifyHealthMsg(source, from, 0f, -fromAmount, 0f, powerID, powerName, trains, effectID); + } else { + from.modifyStamina(-fromAmount, source); + mhmFrom = new ModifyHealthMsg(source, from, 0f, 0f, -fromAmount, powerID, powerName, trains, effectID); + } + + DispatchMessage.sendToAllInRange(to, mhmTo); + DispatchMessage.sendToAllInRange(from, mhmFrom); + + } + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/TransformPowerAction.java b/src/engine/powers/poweractions/TransformPowerAction.java new file mode 100644 index 00000000..cd4a95b3 --- /dev/null +++ b/src/engine/powers/poweractions/TransformPowerAction.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.jobs.FinishEffectTimeJob; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.EffectsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + + +public class TransformPowerAction extends AbstractPowerAction { + + private String effectID; + private EffectsBase effect; + + public TransformPowerAction(ResultSet rs, HashMap effects) throws SQLException { + super(rs); + + this.effectID = rs.getString("effectID"); + this.effect = effects.get(this.effectID); + } + + public String getEffectID() { + return this.effectID; + } + + public EffectsBase getEffect() { + return this.effect; + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + if (this.effect == null || pb == null || ab == null) { + //TODO log error here + return; + } + + int duration = ab.getDuration(trains); + String stackType = ab.getStackType(); + stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; + FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect); + if (duration > 0) + awo.addEffect(stackType, duration, eff, effect, trains); + this.effect.startEffect(source, awo, trains, eff); + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/powers/poweractions/TreeChokePowerAction.java b/src/engine/powers/poweractions/TreeChokePowerAction.java new file mode 100644 index 00000000..d99be3c2 --- /dev/null +++ b/src/engine/powers/poweractions/TreeChokePowerAction.java @@ -0,0 +1,43 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.powers.poweractions; + +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.AbstractWorldObject; +import engine.powers.ActionsBase; +import engine.powers.PowersBase; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class TreeChokePowerAction extends AbstractPowerAction { + + public TreeChokePowerAction(ResultSet rs) throws SQLException { + super(rs); + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + + } + + @Override + protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) { + } + + @Override + protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, + int numTrains, ActionsBase ab, PowersBase pb, int duration) { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/server/MBServerStatics.java b/src/engine/server/MBServerStatics.java new file mode 100644 index 00000000..80338e63 --- /dev/null +++ b/src/engine/server/MBServerStatics.java @@ -0,0 +1,414 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.server; + +import engine.Enum; +import engine.math.Vector3fImmutable; + +public class MBServerStatics { + + public static final int revisionNumber = 1; + public static final String CMDLINE_ARGS_EXE_NAME_DELIMITER = "-name"; + public static final String CMDLINE_ARGS_CONFIG_FILE_PATH_DELIMITER = "-config"; + public static final String CMDLINE_ARGS_CALLER_DELIMITER = "-caller"; + public static final String CMDLINE_ARGS_REASON_DELIMITER = "-reason"; + public static final String EXISTING_CONNECTION_CLOSED = "An existing connection was forcibly closed by the remote host"; + public static final String RESET_BY_PEER = "Connection reset by peer"; + /* + * ####Debugging Flags#### + */ + public static final boolean POWERS_DEBUG = false; + public static final boolean MOVEMENT_SYNC_DEBUG = false; + public static final boolean BONUS_TRAINS_ENABLED = false; + public static final boolean REGENS_DEBUG = false; + public static final boolean SHOW_SAFE_MODE_CHANGE = false; + public static final boolean COMBAT_TARGET_HITBOX_DEBUG = false; // output + // hit box + // calcs + public static final boolean PRINT_INCOMING_OPCODES = false; // print + public static final int BANK_GOLD_LIMIT = 25000000; + // incoming + // opcodes to + // console + public static final int PLAYER_GOLD_LIMIT = 10000000; + // buildings, npcs + /* + * Login cache flags + */ + public static final boolean SKIP_CACHE_LOGIN = false; // skip caching // login server + public static final boolean SKIP_CACHE_LOGIN_PLAYER = false; // skip caching // on login + public static final boolean SKIP_CACHE_LOGIN_ITEM = false; // skip caching + /* + * ChatManager related + */ + public static final int SHOUT_PERCEPTION_RADIUS_MOD = 2; + /* + * DevCmd related + */ + public static final String DEV_CMD_PREFIX = "./"; + // The number of elements in INITIAL_WORKERS defines the initial number of + // job pools + public static final int[] INITIAL_JOBPOOL_WORKERS = {4, 2, 1}; + + /* + * JobManager related + */ + public static final int JOBMANAGER_INTERNAL_MONITORING_INTERVAL_MS = 1000; + public static final int JOB_STALL_THRESHOLD_MS = 120 * 1000; + public static final int SCHEDULER_INITIAL_CAPACITY = 1000; + public static final int SCHEDULER_EXECUTION_TIME_COMPENSATION = 16; + public static final int CHM_INIT_CAP = 10; + + /* + * Concurrent Hash Map - Defaults + */ + public static final float CHM_LOAD = 0.75f; + public static final int CHM_THREAD_HIGH = 4; + public static final int CHM_THREAD_MED = 2; + public static final int CHM_THREAD_LOW = 1; + /* + * LoginErrorMsg related + */ + public static final int LOGINERROR_INVALID_USERNAME_PASSWORD = 1; + /* + * Message is Version: + */ + public static final int LOGINERROR_INCORRECT_CLIENT_VERSION = 3; + public static final int LOGINERROR_NOT_ALLOWED_TO_LOGIN_YET = 4; + /* + * Message is 'Error =' + */ + public static final int LOGINERROR_LOGINSERVER_IS_UNAVAILABLE = 5; + public static final int LOGINERROR_INVALID_ADMIN_USERNAME_PASSWORD = 6; + public static final int LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT = 7; + public static final int LOGINERROR_ACCOUNT_DOESNT_HAVE_SUBSCRIPTION = 8; + public static final int LOGINERROR_ACCOUNT_INSECURE_CHANGE_PASSWORD = 9; + public static final int LOGINERROR_TOO_MANY_LOGIN_TRIES = 10; + /* + * Message is 'Error =' + */ + public static final int LOGINERROR_NOMOREPLAYTIME = 7; + public static final int LOGINERROR_INACTIVE = 8; + public static final int LOGINERROR_UNABLE_TO_LOGIN = 11; + public static final int LOGINERROR_LOGINSERVER_BUSY = 12; + public static final int LOGINERROR_BLANK = 13; + /* + * Name Validation Related + */ + public static final int INVALIDNAME_FIRSTNAME_MUST_BE_LONGER = 1; + public static final int INVALIDNAME_FIRSTANDLAST_MUST_BE_SHORTER = 2; + public static final int INVALIDNAME_FIRSTNAME_MUST_NOT_HAVE_SPACES = 3; + public static final int INVALIDNAME_FIRSTNAME_INVALID_CHARACTERS = 4; + public static final int INVALIDNAME_PLEASE_CHOOSE_ANOTHER_FIRSTNAME = 5; + public static final int INVALIDNAME_PLEASE_CHOOSE_ANOTHER_LASTNAME = 7; + public static final int INVALIDNAME_LASTNAME_UNAVAILABLE = 8; + public static final int INVALIDNAME_FIRSTNAME_UNAVAILABLE = 9; + public static final int INVALIDNAME_WRONG_WORLD_ID = 10; + public static final int INVALIDNAME_GENERIC = 11; + /* + * 1: A first name of at least 3 character(s) must be entered 2: Your first + * and last name cannot be more than 15 characters each. 3: Your first name + * may not contain spaces 4: There are invalid characters in the first name. + * 5: Please choose another first name 7: Please choose another last name 8: + * That last name is unavailable 9: That first name is unavailable 10: Your + * client sent an invalid world id 11: Invalid name. Choose another + */ + public static final int MIN_NAME_LENGTH = 3; + public static final int MAX_NAME_LENGTH = 15; + /* + * ClientConnection related + */ + public static final boolean TCP_NO_DELAY_DEFAULT = true; + public static final byte MAX_CRYPTO_INIT_TRIES = 10; + public static final byte MAX_LOGIN_ATTEMPTS = 5; + public static final int RESET_LOGIN_ATTEMPTS_AFTER = (15 * 60 * 1000); // in + /* + * Account Related + */ + public static final int MAX_ACTIVE_GAME_ACCOUNTS_PER_DISCORD_ACCOUNT = 4; // 0 + public static final byte MAX_NUM_OF_CHARACTERS = 7; + public static final int STAT_STR_ID = 0x8AC3C0E6; + public static final int STAT_SPI_ID = 0xACB82E33; + public static final int STAT_CON_ID = 0xB15DC77E; + public static final int STAT_DEX_ID = 0xE07B3336; + public static final int STAT_INT_ID = 0xFF665EC3; + public static final int SLOT_UNEQUIPPED = 0; + public static final int SLOT_MAINHAND = 1; + public static final int SLOT_OFFHAND = 2; + public static final int SLOT_HELMET = 3; + public static final int SLOT_CHEST = 4; + public static final int SLOT_ARMS = 5; + public static final int SLOT_GLOVES = 6; + public static final int SLOT_RING1 = 7; + public static final int SLOT_RING2 = 8; + public static final int SLOT_NECKLACE = 9; + public static final int SLOT_LEGGINGS = 10; + public static final int SLOT_FEET = 11; + public static final int SLOT_HAIRSTYLE = 18; // 17 & 18? Weird. + public static final int SLOT_BEARDSTYLE = 17; // 17 & 18? Weird. + /* + * Group Formation Names + */ + public static final String[] FORMATION_NAMES = {"Column", "Line", "Box", + "Triangle", "Circle", "Ranks", "Wedge", "Inverse Wedge", "T"}; + public static final int RUNE_COST_ATTRIBUTE_ID = 0; + + // Equip[0] = Slot1 = Weapon MainHand + // Equip[1] = Slot2 = OffHand + // Equip[2] = Slot3 = Helmet + // Equip[3] = Slot4 = Chest + // Equip[4] = Slot5 = Arms + // Equip[5] = Slot6 = Gloves + // Equip[6] = Slot7 = Ring1 + // Equip[7] = Slot8 = Ring2 + // Equip[8] = Slot9 = Necklace + // Equip[9] = Slot10 = Leggings + // Equip[10] = Slot11 = Feet + // Equip[11] = Slot17 = HairStyle + // Equip[12] = Slot18 = BeardStyle + public static final int RUNE_STR_ATTRIBUTE_ID = 1; + public static final int RUNE_DEX_ATTRIBUTE_ID = 2; + public static final int RUNE_CON_ATTRIBUTE_ID = 3; + public static final int RUNE_INT_ATTRIBUTE_ID = 4; + public static final int RUNE_SPI_ATTRIBUTE_ID = 5; + public static final int RUNE_STR_MAX_ATTRIBUTE_ID = 6; + public static final int RUNE_DEX_MAX_ATTRIBUTE_ID = 7; + public static final int RUNE_CON_MAX_ATTRIBUTE_ID = 8; + public static final int RUNE_INT_MAX_ATTRIBUTE_ID = 9; + public static final int RUNE_SPI_MAX_ATTRIBUTE_ID = 10; + public static final int RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID = 11; + public static final int RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID = 12; + public static final int RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID = 13; + public static final int RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID = 14; + public static final int RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID = 15; + /* + * DBMan + */ + public static final int NO_DB_ROW_ASSIGNED_YET = Integer.MAX_VALUE; + public static final boolean DB_DEBUGGING_ON_BY_DEFAULT = false; // warning: + public static final boolean ENABLE_QUERY_TIME_WARNING = true; + public static final boolean ENABLE_UPDATE_TIME_WARNING = true; + public static final boolean ENABLE_EXECUTION_TIME_WARNING = true; + public static final int AFK_TIMEOUT_MS = (30 * 60 * 1000) * 100; // Added + public static final int TIMEOUT_CHECKS_TIMER_MS = (60 * 1000); + public static final int MASK_PLAYER = 1; + public static final int MASK_MOB = 2; + + /* + * Masks for Quad Tree. Masks should be multiple of 2. + */ + public static final int MASK_PET = 4; + public static final int MASK_CORPSE = 8; + public static final int MASK_BUILDING = 16; + public static final int MASK_UNDEAD = 64; + public static final int MASK_BEAST = 128; + public static final int MASK_HUMANOID = 256; + public static final int MASK_NPC = 512; + public static final int MASK_IAGENT = 2048; + public static final int MASK_DRAGON = 4096; + public static final int MASK_RAT = 8192; + public static final int MASK_SIEGE = 16384; + public static final int MASK_CITY = 32768; + public static final int MASK_ZONE = 65536; + public static final int MASK_AGGRO = 5; // Player, Pet + public static final int MASK_MOBILE = 7; // Player, Mob, Pet + + /* + * Combined QT Masks. For convenience + */ + public static final int MASK_STATIC = 568; // Corpse, Building, Trigger, NPC + /* + * World Coordinate Data + */ + public static final double MAX_WORLD_HEIGHT = -98304.0; + public static final double MAX_WORLD_WIDTH = 131072.0; + public static final float SEA_FLOOR_ALTITUDE = -1000f; + /* + * Ranges + */ + public static final int CHARACTER_LOAD_RANGE = 400; // load range of mobile objects + // (default: 300) + public static final int STRUCTURE_LOAD_RANGE = 700; // load range of + public static final int EXP_RANGE = 400; + public static final int GOLD_SPLIT_RANGE = 600; + // non-moving objects + public static final int SAY_RANGE = 200; + public static final int SHOUT_RANGE = 300; + public static final int STATIC_THRESHOLD = 75; // Range must travel before + // reloading statics + public static final int FORMATION_RANGE = 75; // Max Distance a player can + // (default: 600) + // be from group lead on + // formation move + public static final int OPENCLOSEDOORDISTANCE = 128; // Max distance a + public static final int DOOR_CLOSE_TIMER = 30000; // 30 seconds + // player can be from a door in order to toggle its state + public static final int TRADE_RANGE = 10; // Max distance a player can be + // from another player to trade + public static final int NPC_TALK_RANGE = 20; // Range player can be to talk + // to npc + public static final int MAX_TELEPORT_RANGE = 1020; // Max range teleports + // will work at + public static final int RANGED_WEAPON_RANGE = 35; // any weapon attack + // range beyond this + // is ranged. + public static final int CALL_FOR_HELP_RADIUS = 100; // Range mobs will + public static final int TREE_TELEPORT_RADIUS = 30; + public static final int[] DEFAULTGRID = {-1, 1}; + public static final float startX = 19128;// 70149f; //19318.0f; + public static final float startY = 94f; // 94f; + public static final float startZ = -73553; // -73661.0f; + public static final Vector3fImmutable DEFAULT_START = new Vector3fImmutable( + MBServerStatics.startX, MBServerStatics.startY, + MBServerStatics.startZ); + /* + * Base movement speeds. Do NOT modify these. They must match the client + */ + public static final float FLYWALKSPEED = 6.33f; + // respond to calls + // for help + public static final float FLYRUNSPEED = 18.38f; + public static final float SWIMSPEED = 6.5f; + public static final float WALKSPEED = 6.5f; + public static final float RUNSPEED = 14.67f; + public static final float COMBATWALKSPEED = 4.44f; + + + /* + * Noob Island Start Location for new players + */ + public static final float COMBATRUNSPEED = 14.67f; + public static final float RUNSPEED_MOB = 15.4f; + public static final float MOVEMENT_DESYNC_TOLERANCE = 2f; // Distance out of + public static final float NO_WEAPON_RANGE = 8f; // Range for attack with no + public static final float REGEN_IDLE = .06f; + /* + * Base regen rates. Do NOT modify these. They must match the client %per + * second for health/mana. x per second for stamina. + */ + public static final float HEALTH_REGEN_SIT = 0.0033333f; // 100% in 3 + // minutes + public static final float HEALTH_REGEN_IDLE = 0.000666667f; // 100% in 25 + // minutes + public static final float HEALTH_REGEN_WALK = 0.0005f; // 100% in 33.33 + // minutes + public static final float HEALTH_REGEN_RUN = 0f; + public static final float HEALTH_REGEN_SWIM_NOSTAMINA = -.03f; // 100% in + public static final float HEALTH_REGEN_SIT_STATIC = 0.33333f; // 100% in 3 + // minutes + public static final float HEALTH_REGEN_IDLE_STATIC = 0.0666667f; // 100% in + // 25 + // minutes + public static final float HEALTH_REGEN_WALK_STATIC = 0.05f; // 100% in 33.33 + // minutes + public static final float HEALTH_REGEN_RUN_STATIC = 0f; + public static final float HEALTH_REGEN_SWIM_NOSTAMINA_STATIC = 0f; // 100% + // weapon + public static final float MANA_REGEN_STATIC = 0.16666666666666666666666666666667f; + public static final float MANA_REGEN_SIT = 0.008333333f; // 100% in 2 + public static final float MANA_REGEN_IDLE = 0.00166667f; // 100% in 10 + public static final float MANA_REGEN_WALK = 0.00125f; // 100% in 13.333 + public static final float MANA_REGEN_RUN = 0f; + public static final float STAMINA_REGEN_SIT = 2f; // 2 per second + public static final float STAMINA_REGEN_IDLE = 0.2f; // 1 per 5 seconds + public static final float STAMINA_REGEN_WALK = 0f; + public static final float STAMINA_REGEN_RUN_COMBAT = -0.6499999762f; + public static final float STAMINA_REGEN_RUN_NONCOMBAT = -0.400000006f; + public static final float STAMINA_REGEN_SWIM = -1f; // -1 per second + public static final int REGEN_SENSITIVITY_PLAYER = 250; // calc regen ever X + public static final int REGEN_SENSITIVITY_MOB = 1000; // calc regen ever X + public static final int TOMBSTONE = 2024; + public static final int LOGOUT_TIMER_MS = 1000; // logout delay applied + public static final int CORPSE_CLEANUP_TIMER_MS = 15 * 60 * 1000; // Cleanup + public static final int DEFAULT_SPAWN_TIME_MS = 3 * 60 * 1000; // 3 minute + public static final int SESSION_CLEANUP_TIMER_MS = 30 * 1000; // cleanup + public static final int MOVEMENT_FREQUENCY_MS = 1000; // Update movement + // once every X ms + public static final int FLY_FREQUENCY_MS = 1000; // Update flight once every + public static final float FLY_RATE = .0078f; + // x ms + public static final int HEIGHT_CHANGE_TIMER_MS = 125; // Time in ms to fly + // up or down 1 unit + public static final long OPCODE_HANDLE_TIME_WARNING_MS = 250L; + public static final long DB_QUERY_WARNING_TIME_MS = 250L; + public static final long DB_UPDATE_WARNING_TIME_MS = 250L; + public static final long DB_EXECUTION_WARNING_TIME_MS = 250L; + // summons + public static final int THIRTY_SECONDS = 30000; + public static final int FOURTYFIVE_SECONDS = 45000; + public static final int ONE_MINUTE = 60000; + public static final int FIVE_MINUTES = 300000; + public static final int FIFTEEN_MINUTES = 900000; + public static final int THIRTY_MINUTES = 1800000; + public static final long TWENTY_FOUR_HOURS = 86400000; + public static final int LOAD_OBJECT_DELAY = 500; // long to wait to update + public static final int TELEPORT_TIME_IN_SECONDS = 10; + public static final int REPLEDGE_TIME_IN_SECONDS = 0; + public static final int RUNEGATE_CLOSE_TIME = 30000; // runegate close timer + public static final long PLAYER_KILL_XP_TIMER = 60 * 60 * 1000; // 60 + public static final int UPDATE_GROUP_RATE = 10000; // Update group info + public static final int COMBAT_SEND_DODGE = 20; + public static final int COMBAT_SEND_BLOCK = 21; + public static final int COMBAT_SEND_PARRY = 22; + public static final short LEVELCAP = 75; + public static final int LEVEL_CON_WHITE = 7; + public static final int RESPAWN_TIMER = 90 * 1000; + public static final int DESPAWN_TIMER = 12 * 1000; + public static final int DESPAWN_TIMER_WITH_LOOT = 60 * 1000; + public static final int DESPAWN_TIMER_ONCE_LOOTED = 5 * 1000; + public static final int MAX_COMBAT_HITBOX_RADIUS = 80; + public static final int PROC_CHANCE = 5; // %chance to proc + public static final float TRACK_ARROW_FAST_RANGE = 50f; // Range to go from + public static final int TRACK_ARROW_SENSITIVITY = 1000; // Refresh track + // arrows every X ms + public static final int TRACK_ARROW_SENSITIVITY_FAST = 250; // Refresh track + public static final int LOW_POPULATION = 100; + public static final int NORMAL_POPULATION = 500; + public static final int HIGH_POPULATION = 1000; + public static final int VERY_OVERPOPULATED_POPULATION = 3000; + public static final int FULL_POPULATION = 5000; + public static final int TRACK_WINDOW_THRESHOLD = 1000; // max refresh once + public static final int WHO_WINDOW_THRESHOLD = 3000; // max refresh once + // Mine related + public static final int MINE_EARLY_WINDOW = 16; // 4pm + public static final int MINE_LATE_WINDOW = 0; // Midnight + public static boolean DEBUG_PROTOCOL = false; + public static int SPATIAL_HASH_BUCKETSX = 16384; + public static int SPATIAL_HASH_BUCKETSY = 12288; + public static float MAX_PLAYER_X_LOC = 129999; + public static float MAX_PLAYER_Y_LOC = -97000; + public static float LOOT_RANGE = 100; + public static float MOB_SPEED_WALK = 6.5f; + public static float MOB_SPEED_WALKCOMBAT = 4.4f; + public static float MOB_SPEED_RUN = 14.67f; + public static float MOB_SPEED_RUNCOMBAT = 14.67f; + public static float STAMINA_REGEN_FLY_IDLE = -2f; // needs verifying + public static float STAMINA_REGEN_FLY_WALK = -1f; // needs verifying + public static float STAMINA_REGEN_FLY_RUN = -1.400000006f; // needs verifying + public static float STAMINA_REGEN_FLY_RUN_COMBAT = -1.6499999762f; // needs verifying + public static boolean DB_ENABLE_QUERY_OUTPUT = false; + public static float PLAYER_HATE_DELIMITER = 50; // reduces 50 hate a second + public static float PLAYER_COMBAT_HATE_MODIFIER = 2; + // DO NOT FINAL THESE FIELD! + public static Enum.AccountStatus accessLevel; // Min account level to login to server + public static boolean blockLogin = false; + public static boolean ENABLE_VAULT_FILL = false; + public static boolean ENABLE_MOB_LOOT = true; + public static boolean ENABLE_AUDIT_JOB_WORKERS = true; + public static boolean ENABLE_COMBAT_TARGET_HITBOX = true; + public static String JUNIOR = "Junior"; + public static String VETERAN = "Veteran"; + public static String ELITE = "Elite"; + + public static String getEmulatorVersion() { + return Integer.toString(revisionNumber); + } + +} diff --git a/src/engine/server/login/LoginServer.java b/src/engine/server/login/LoginServer.java new file mode 100644 index 00000000..cdaaf1d3 --- /dev/null +++ b/src/engine/server/login/LoginServer.java @@ -0,0 +1,512 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.server.login; + +import engine.Enum; +import engine.gameManager.*; +import engine.job.JobScheduler; +import engine.jobs.CSessionCleanupJob; +import engine.net.Network; +import engine.net.client.ClientConnection; +import engine.net.client.ClientConnectionManager; +import engine.net.client.Protocol; +import engine.net.client.msg.login.ServerStatusMsg; +import engine.net.client.msg.login.VersionInfoMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import engine.server.world.WorldServer; +import engine.util.ByteUtils; +import engine.util.ThreadUtils; +import org.pmw.tinylog.Configurator; +import org.pmw.tinylog.Level; +import org.pmw.tinylog.Logger; +import org.pmw.tinylog.labelers.TimestampLabeler; +import org.pmw.tinylog.policies.StartupPolicy; +import org.pmw.tinylog.writers.RollingFileWriter; + +import java.io.*; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.Iterator; + +import static java.lang.System.exit; + +public class LoginServer { + + + // Instance variables + + public static int population = 0; + public static boolean worldServerRunning = false; + public static boolean loginServerRunning = false; + public static ServerStatusMsg serverStatusMsg = new ServerStatusMsg(0, (byte) 1); + private VersionInfoMsg versionInfoMessage; + + // This is the entrypoint for the MagicBane Login Server when + // it is executed by the command line scripts. The fun begins here! + + public LoginServer() { + + } + + // Mainline execution loop for the login server. + + public static void main(String[] args) { + + LoginServer loginServer; + + // Initialize TinyLog logger with our own format + + Configurator.defaultConfig() + .addWriter(new RollingFileWriter("logs/login/login.txt", 30, new TimestampLabeler(), new StartupPolicy())) + .level(Level.DEBUG) + .formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}") + .activate(); + + try { + + // Configure the the Login Server + + loginServer = new LoginServer(); + ConfigManager.loginServer = loginServer; + ConfigManager.handler = new LoginServerMsgHandler(loginServer); + + ConfigManager.serverType = Enum.ServerType.LOGINSERVER; + + if (ConfigManager.init() == false) { + Logger.error("ABORT! Missing config entry!"); + return; + } + + // Start the Login Server + + loginServer.init(); + loginServer.exec(); + + exit(0); + + } catch (Exception e) { + Logger.error(e); + e.printStackTrace(); + exit(1); + } + } + + // Constructor + + public static boolean getActiveBaneQuery(PlayerCharacter playerCharacter) { + + boolean outStatus = false; + + // char has never logged on so cannot have dropped a bane + + if (playerCharacter.getHash() == null) + return outStatus; + + // query data warehouse for unresolved bane with this character + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = buildQueryActiveBaneStatement(connection, playerCharacter); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + outStatus = true; + } + + } catch (SQLException e) { + Logger.error(e.toString()); + } + + return outStatus; + } + + private static PreparedStatement buildQueryActiveBaneStatement(Connection connection, PlayerCharacter playerCharacter) throws SQLException { + PreparedStatement outStatement; + String queryString = "SELECT `city_id` FROM `warehouse_banehistory` WHERE `char_id` = ? AND `RESOLUTION` = 'PENDING'"; + outStatement = connection.prepareStatement(queryString); + outStatement.setString(1, playerCharacter.getHash()); + return outStatement; + + } + + public static boolean isPortInUse(int port) { + + ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "lsof -i tcp:" + port + " | tail -n +2 | awk '{print $2}'"); + builder.redirectErrorStream(true); + Process process = null; + String line = null; + boolean portInUse = false; + + try { + process = builder.start(); + + InputStream is = process.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + while ((line = reader.readLine()) != null) { + portInUse = true; + } + + } catch (IOException e) { + e.printStackTrace(); + } + + return portInUse; + } + + private void exec() { + + + LocalDateTime nextCacheTime = LocalDateTime.now(); + LocalDateTime nextServerTime = LocalDateTime.now(); + LocalDateTime nextDatabaseTime = LocalDateTime.now(); + + loginServerRunning = true; + + while (true) { + + // Invalidate cache for players driven by forum + // and stored procedure forum_link_pass() + + // Run cache routine right away if requested. + + File cacheFile = new File("cacheInvalid"); + + + if (cacheFile.exists() == true) { + + nextCacheTime = LocalDateTime.now(); + + try { + Files.deleteIfExists(Paths.get("cacheInvalid")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + if (LocalDateTime.now().isAfter(nextCacheTime)) { + invalidateCacheList(); + nextCacheTime = LocalDateTime.now().plusSeconds(30); + } + + if (LocalDateTime.now().isAfter(nextServerTime)) { + checkServerHealth(); + nextServerTime = LocalDateTime.now().plusSeconds(1); + } + + if (LocalDateTime.now().isAfter(nextDatabaseTime)) { + String pop = SimulationManager.getPopulationString(); + Logger.info("Keepalive: " + pop); + nextDatabaseTime = LocalDateTime.now().plusMinutes(30); + } + + ThreadUtils.sleep(100); + + } + } + + private boolean init() { + + // Initialize Application Protocol + + Protocol.initProtocolLookup(); + + // Configure the VersionInfoMsgs: + + this.versionInfoMessage = new VersionInfoMsg(ConfigManager.MB_MAJOR_VER.getValue(), + ConfigManager.MB_MINOR_VER.getValue()); + + Logger.info("Initializing Database layer"); + initDatabaseLayer(); + + Logger.info("Initializing Network"); + Network.init(); + + Logger.info("Initializing Client Connection Manager"); + initClientConnectionManager(); + + // instantiate AccountManager + Logger.info("Initializing SessionManager."); + + // Sets cross server behavior + SessionManager.setCrossServerBehavior(0); + + // activate powers manager + Logger.info("Initializing PowersManager."); + PowersManager.initPowersManager(false); + + RuneBaseAttribute.LoadAllAttributes(); + RuneBase.LoadAllRuneBases(); + BaseClass.LoadAllBaseClasses(); + Race.loadAllRaces(); + RuneBaseEffect.LoadRuneBaseEffects(); + + Logger.info("Initializing Blueprint data."); + Blueprint.loadAllBlueprints(); + + Logger.info("Loading Kits"); + DbManager.KitQueries.GET_ALL_KITS(); + + Logger.info("Initializing ItemBase data."); + ItemBase.loadAllItemBases(); + + Logger.info("Initializing Race data"); + Enum.RaceType.initRaceTypeTables(); + Race.loadAllRaces(); + + Logger.info("Initializing Errant Guild"); + Guild.getErrantGuild(); + + Logger.info("Loading All Guilds"); + DbManager.GuildQueries.GET_ALL_GUILDS(); + + + Logger.info("***Boot Successful***"); + return true; + } + /* + * message handlers (relay) + */ + + // ============================== + // Support Functions + // ============================== + + private boolean initDatabaseLayer() { + + // Try starting a GOM <-> DB connection. + try { + + Logger.info("Configuring Magicbane to use Database: '" + + ConfigManager.MB_DATABASE_NAME.getValue() + "' on " + + ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':' + + ConfigManager.MB_DATABASE_PORT.getValue()); + + DbManager.configureConnectionPool(); + + } catch (Exception e) { + Logger.error(e.getMessage()); + return false; + } + + PreparedStatementShared.submitPreparedStatementsCleaningJob(); + + if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) { + PreparedStatementShared.enableDebugging(); + } + + return true; + } + + public void removeClient(ClientConnection conn) { + if (conn == null) { + Logger.info( + "ClientConnection null in removeClient."); + return; + } + String key = ByteUtils.byteArrayToSafeStringHex(conn + .getSecretKeyBytes()); + + CSessionCleanupJob cscj = new CSessionCleanupJob(key); + + JobScheduler.getInstance().scheduleJob(cscj, + MBServerStatics.SESSION_CLEANUP_TIMER_MS); + } + + private void initClientConnectionManager() { + + try { + + String name = ConfigManager.MB_WORLD_NAME.getValue(); + + + if (ConfigManager.MB_EXTERNAL_ADDR.getValue().equals("0.0.0.0")) { + + // Autoconfigure External address for use in worldserver response + + Logger.info("AUTOCONFIG EXTERNAL IP ADDRESS"); + URL whatismyip = new URL("http://checkip.amazonaws.com"); + + BufferedReader in = new BufferedReader(new InputStreamReader( + whatismyip.openStream())); + ConfigManager.MB_EXTERNAL_ADDR.setValue(in.readLine()); + } + + if (ConfigManager.MB_BIND_ADDR.getValue().equals("0.0.0.0")) { + + try (final DatagramSocket socket = new DatagramSocket()) { + socket.connect(InetAddress.getByName("8.8.8.8"), 10002); + ConfigManager.MB_BIND_ADDR.setValue(socket.getLocalAddress().getHostAddress()); + } + + } + + Logger.info("External address: " + ConfigManager.MB_EXTERNAL_ADDR.getValue() + ":" + ConfigManager.MB_LOGIN_PORT.getValue()); + Logger.info("Internal address: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_LOGIN_PORT.getValue()); + + InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue()); + int port = Integer.parseInt(ConfigManager.MB_LOGIN_PORT.getValue()); + + ClientConnectionManager connectionManager = new ClientConnectionManager(name + ".ClientConnMan", addy, + port); + connectionManager.startup(); + + } catch (IOException e) { + Logger.error(e.toString()); + } + } + + public VersionInfoMsg getDefaultVersionInfo() { + return versionInfoMessage; + } + + //this updates a server being up or down without resending the entire char select screen. + public void updateServersForAll(boolean isRunning) { + + try { + + Iterator i = SessionManager.getAllActiveClientConnections().iterator(); + + while (i.hasNext()) { + + ClientConnection clientConnection = i.next(); + + if (clientConnection == null) + continue; + + Account ac = clientConnection.getAccount(); + + if (ac == null) + continue; + + boolean isUp = isRunning; + + + if (WorldServer.worldAccessLevel.ordinal() > ac.status.ordinal()) + isUp = false; + + LoginServer.serverStatusMsg.setServerID(WorldServer.worldMapID); + LoginServer.serverStatusMsg.setIsUp(isUp ? (byte) 1 : (byte) 0); + clientConnection.sendMsg(LoginServer.serverStatusMsg); + } + } catch (Exception e) { + Logger.error(e); + e.printStackTrace(); + } + } + + public void checkServerHealth() { + + // Check if worldserver is running + + if (!isPortInUse(Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue()))) { + worldServerRunning = false; + population = 0; + updateServersForAll(worldServerRunning); + return; + } + + // Worldserver is running and writes a polling file. + // Read the current population count from the server and + // update player displays accordingly. + + worldServerRunning = true; + population = readPopulationFile(); + updateServersForAll(worldServerRunning); + + } + + public void invalidateCacheList() { + + int objectUUID; + String objectType; + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = connection.prepareStatement("SELECT * FROM `login_cachelist`"); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + objectUUID = rs.getInt("UID"); + objectType = rs.getString("type"); + + Logger.info("INVALIDATED : " + objectType + " UUID: " + objectUUID); + + switch (objectType) { + + case "account": + DbManager.removeFromCache(Enum.GameObjectType.Account, objectUUID); + break; + case "character": + DbManager.removeFromCache(Enum.GameObjectType.PlayerCharacter, objectUUID); + PlayerCharacter player = (PlayerCharacter) DbManager.getObject(Enum.GameObjectType.PlayerCharacter, objectUUID); + PlayerCharacter.initializePlayer(player); + player.getAccount().characterMap.replace(player.getObjectUUID(), player); + Logger.info("Player active state is : " + player.isActive()); + break; + } + + } + + } catch (SQLException e) { + Logger.info(e.toString()); + } + + // clear the db table + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = connection.prepareStatement("DELETE FROM `login_cachelist`")) { + + statement.execute(); + + } catch (SQLException e) { + Logger.info(e.toString()); + } + + + } + + private int readPopulationFile() { + + ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "cat " + ConfigManager.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'", "") + ".pop"); + builder.redirectErrorStream(true); + Process process = null; + String line = null; + int population = 0; + + try { + + process = builder.start(); + + InputStream is = process.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + while ((line = reader.readLine()) != null) { + population = Integer.parseInt(line); + } + + } catch (IOException e) { + e.printStackTrace(); + return 0; + } + + return population; + } + +} diff --git a/src/engine/server/login/LoginServerMsgHandler.java b/src/engine/server/login/LoginServerMsgHandler.java new file mode 100644 index 00000000..dff76ce7 --- /dev/null +++ b/src/engine/server/login/LoginServerMsgHandler.java @@ -0,0 +1,459 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.server.login; + +import engine.Enum; +import engine.Enum.DispatchChannel; +import engine.Enum.GameObjectType; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.job.JobScheduler; +import engine.jobs.DisconnectJob; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.NetMsgHandler; +import engine.net.client.ClientConnection; +import engine.net.client.Protocol; +import engine.net.client.msg.ClientNetMsg; +import engine.net.client.msg.ServerInfoMsg; +import engine.net.client.msg.login.*; +import engine.objects.Account; +import engine.objects.GuildStatusController; +import engine.objects.PlayerCharacter; +import engine.server.MBServerStatics; +import engine.session.CSSession; +import engine.session.Session; +import engine.util.ByteUtils; +import engine.util.StringUtils; +import org.pmw.tinylog.Logger; + +public class LoginServerMsgHandler implements NetMsgHandler { + + private final LoginServer server; + + LoginServerMsgHandler(LoginServer server) { + super(); + this.server = server; + } + + public static void sendInvalidNameMsg(String firstName, String lastName, int errorCode, ClientConnection clientConnection) { + + InvalidNameMsg invalidNameMessage; + + if (firstName.length() > 256 || lastName.length() > 256) + invalidNameMessage = new InvalidNameMsg(firstName, lastName, errorCode); + else + invalidNameMessage = new InvalidNameMsg(firstName, lastName, errorCode); + + clientConnection.sendMsg(invalidNameMessage); + } + + /* + * ========================================================================= + * Client Messages + * ========================================================================= + */ + @Override + public boolean handleClientMsg(ClientNetMsg clientNetMsg) { + + if (clientNetMsg == null) { + Logger.error("Recieved null msg. Returning."); + return false; + } + + ClientConnection origin = (ClientConnection) clientNetMsg.getOrigin(); + Protocol protocolMsg = clientNetMsg.getProtocolMsg(); + + try { + + switch (protocolMsg) { + + case VERSIONINFO: + this.VerifyCorrectClientVersion((VersionInfoMsg) clientNetMsg); + break; + + case LOGIN: + if (LoginServer.loginServerRunning == true) + this.Login((ClientLoginInfoMsg) clientNetMsg, origin); + else + this.KickToLogin(MBServerStatics.LOGINERROR_LOGINSERVER_BUSY, "", origin); + break; + + case KEEPALIVESERVERCLIENT: + // echo the keep alive back + origin.sendMsg(clientNetMsg); + break; + + case SELECTSERVER: + this.SendServerInfo(origin); + break; + + case CREATECHAR: + this.CommitNewCharacter((CommitNewCharacterMsg) clientNetMsg, origin); + break; + + case REMOVECHAR: + this.DeleteCharacter((DeleteCharacterMsg) clientNetMsg, origin); + break; + + case SELECTCHAR: + this.RequestGameServer((GameServerIPRequestMsg) clientNetMsg, origin); + break; + + case SETSELECTEDOBECT: + // Why is this being sent to login server? + break; + + default: + String ocHex = StringUtils.toHexString(protocolMsg.opcode); + Logger.error("Cannot not handle Opcode: " + ocHex); + return false; + } + + } catch (Exception e) { + Logger.error("protocolMsg:" + protocolMsg + e.toString()); + return false; + } + + return true; + } + + private void VerifyCorrectClientVersion(VersionInfoMsg vim) { + ClientConnection cc; + String cMajorVer; + String cMinorVer; + VersionInfoMsg outVim; + + cc = (ClientConnection) vim.getOrigin(); + cMajorVer = vim.getMajorVersion(); + cMinorVer = vim.getMinorVersion(); + + if (!cMajorVer.equals(this.server.getDefaultVersionInfo().getMajorVersion())) { + this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Major Version Failure: " + cMajorVer, cc); + return; + } + + /* if (!cMinorVer.equals(this.server.getDefaultVersionInfo().getMinorVersion())) { + this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: " + cMinorVer, cc); + return; + } */ + + if (cMinorVer == null) { + this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: ", cc); + return; + } + + if (cMinorVer.length() < 8 || cMinorVer.length() > 16) { + this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: ", cc); + return; + } + + // set MachineID for this connection + + cc.machineID = cMinorVer; + + // send fake right back to the client + outVim = new VersionInfoMsg(vim.getMajorVersion(), this.server.getDefaultVersionInfo().getMinorVersion()); + cc.sendMsg(outVim); + } + + // our data access should be in a separate object + private void Login(ClientLoginInfoMsg clientLoginInfoMessage, ClientConnection clientConnection) { + + // Add zero length strings to eliminate the need for null checking. + String uname = clientLoginInfoMessage.getUname(); + String pass = clientLoginInfoMessage.getPword(); + + // Check to see if there is actually any data in uname.pass + if (uname.length() == 0) { + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "The username provided was zero length.", clientConnection); + return; + } + + if (pass.length() == 0) { + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "The password provided was zero length.", clientConnection); + return; + } + + Account account; + + account = DbManager.AccountQueries.GET_ACCOUNT(uname); + + // Create the account if it doesn't exist and MB_LOGIN_AUTOREG is TRUE; + // This is to support MagicBox users without a web hosting skillset. + + if (account == null) { + + if (ConfigManager.MB_LOGIN_AUTOREG.getValue().equalsIgnoreCase("false")) { + this.KickToLogin(MBServerStatics.LOGINERROR_INVALID_USERNAME_PASSWORD, "Could not find account (" + uname + ')', clientConnection); + Logger.info("Could not find account (" + uname + ')'); + return; + } + + Logger.info("AutoRegister: " + uname + "/" + pass); + DbManager.AccountQueries.CREATE_SINGLE(uname, pass); + account = DbManager.AccountQueries.GET_ACCOUNT(uname); + + if (account == null) { + this.KickToLogin(MBServerStatics.LOGINERROR_INVALID_USERNAME_PASSWORD, "Could not find account (" + uname + ')', clientConnection); + Logger.info("Could not auto-create (" + uname + ')'); + return; + } + } + + if (account.getLastLoginFailure() + MBServerStatics.RESET_LOGIN_ATTEMPTS_AFTER < System.currentTimeMillis()) + account.resetLoginAttempts(); + + // TODO: Log the login attempts IP, name, password and timestamp + // Check number invalid login attempts. If 5 or greater, kick to login. + if (account.getLoginAttempts() >= MBServerStatics.MAX_LOGIN_ATTEMPTS) { + + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Too many login in attempts for '" + uname + '\'', clientConnection); + Logger.info("Too many login in attempts for '" + uname + '\''); + return; + } + + if (account.lastPasswordCheck < System.currentTimeMillis()) { + account.lastPasswordCheck = System.currentTimeMillis() + MBServerStatics.ONE_MINUTE; + } + + // Attempt to validate login + try { + if (!account.passIsValid(pass, clientConnection.getClientIpAddress(), clientConnection.machineID)) { + + account.incrementLoginAttempts(); + this.KickToLogin(MBServerStatics.LOGINERROR_INVALID_USERNAME_PASSWORD, "", clientConnection); + Logger.info("Incorrect password(" + uname + ')'); + return; + } + } catch (IllegalArgumentException e1) { + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "", clientConnection); + Logger.info("Failed forum account validation(" + uname + ')'); + } + + // Account deactivated + + if (account.status.equals(Enum.AccountStatus.BANNED)) { + this.KickToLogin(MBServerStatics.LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT, "", clientConnection); + return; + } + + // Check to see if we have a Session mapped with this Account: + Session session = SessionManager.getSession(account); + + // If there is, then the account is in use and must be handled: + // kick the 'other connection' + if (session != null) + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Your account has been accessed from a different IP & Port.", session.getConn()); // Logout the character + + + // TODO implement character logout + // Get a new session + session = SessionManager.getNewSession(account, clientConnection); + + // Set Invalid Login Attempts to 0 + account.resetLoginAttempts(); + + // Send Login Response + ClientLoginInfoMsg loginResponse = new ClientLoginInfoMsg(clientLoginInfoMessage); + loginResponse.setUnknown06(8323072); + loginResponse.setUnknown07(3276800); + loginResponse.setUnknown08(196608); + loginResponse.setUnknown09((short) 15); + + clientConnection.sendMsg(loginResponse); + + // send character select screen + try { + this.sendCharacterSelectScreen(session); + } catch (Exception e) { + Logger.error("Unable to Send Character Select Screen to client"); + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send Character Select Screen to client.", clientConnection); + return; + } + + // Logging + String addyPort = clientConnection.getRemoteAddressAndPortAsString(); + int id = account.getObjectUUID(); + + Logger.info(uname + '(' + id + ") has successfully logged in from " + addyPort); + + } + + private void KickToLogin(int errCode, String message, ClientConnection origin) { + LoginErrorMsg msg = new LoginErrorMsg(errCode, message); + + PlayerCharacter player = origin.getPlayerCharacter(); + + if (player == null) { + origin.sendMsg(msg); + } else { + Dispatch dispatch = Dispatch.borrow(player, msg); + DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); + } + + + Logger.info("Kicking to Login. Message: '" + message + '\''); + + DisconnectJob dj = new DisconnectJob(origin); + JobScheduler.getInstance().scheduleJob(dj, 250); + } + + protected void sendCharacterSelectScreen(Session s) { + sendCharacterSelectScreen(s, false); + } + + private void sendCharacterSelectScreen(Session s, boolean fromCommit) { + + if (s.getAccount() != null) { + CharSelectScreenMsg cssm = new CharSelectScreenMsg(s, fromCommit); + s.getConn().sendMsg(cssm); + } else { + Logger.error("No Account Found: Unable to Send Character Select Screen"); + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send Character Select Screen to client.", s.getConn()); + } + } + + private void SendServerInfo(ClientConnection conn) { + ServerInfoMsg sim = new ServerInfoMsg(); + + if (!conn.sendMsg(sim)) { + Logger.error("Failed to send message"); + + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send ServerInfoMsg to client.", conn); + } + } + + private void CommitNewCharacter(CommitNewCharacterMsg commitNewCharacterMessage, ClientConnection clientConnection) { + + Session session = SessionManager.getSession(clientConnection); + + if (session.getAccount() == null) + return; + + try { + // Check to see if there is an available slot. + if (session.getAccount().characterMap.size() >= MBServerStatics.MAX_NUM_OF_CHARACTERS) { + this.sendCharacterSelectScreen(session); + return; + } + + PlayerCharacter pc = PlayerCharacter.generatePCFromCommitNewCharacterMsg(session.getAccount(), commitNewCharacterMessage, clientConnection); + + if (pc == null) { + Logger.info("Player returned null while creating character."); + this.sendCharacterSelectScreen(session, true); + return; + } + + PlayerCharacter.initializePlayer(pc); + session.getAccount().characterMap.putIfAbsent(pc.getObjectUUID(), pc); + // Send back to Character Select Screen + this.sendCharacterSelectScreen(session, true); + + } catch (Exception e) { + Logger.error(e); + this.sendCharacterSelectScreen(session, true); + } + } + + private void DeleteCharacter(DeleteCharacterMsg msg, ClientConnection origin) { + + try { + PlayerCharacter playerCharacter; + Session session; + + session = SessionManager.getSession(origin); + playerCharacter = PlayerCharacter.getPlayerCharacter(msg.getCharacterUUID()); + + if (playerCharacter == null) { + Logger.error("Delete Error: PlayerID=" + msg.getCharacterUUID() + " not found."); + this.sendCharacterSelectScreen(session); + return; + } + + if (session.getAccount() == null) { + Logger.error("Delete Error: Account not found."); + this.sendCharacterSelectScreen(session); + return; + } + + if (playerCharacter.getAccount().equals(session.getAccount()) == false) { + Logger.error("Delete Error: Character " + playerCharacter.getName() + " does not belong to account " + origin.getAccount().getUname()); + this.sendCharacterSelectScreen(session); + return; + } + + //Can't delete as Guild Leader + //TODO either find an error or just gdisband. + + if (GuildStatusController.isGuildLeader(playerCharacter.getGuildStatus())) { + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Cannot delete a guild leader.", origin); + return; + } + + // check for active banes + + if (LoginServer.getActiveBaneQuery(playerCharacter)) { + Logger.info("Character " + playerCharacter.getName() + " has unresolved bane"); + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Player has unresolved bane.", origin); + return; + } + + playerCharacter.getAccount().characterMap.remove(playerCharacter.getObjectUUID()); + playerCharacter.deactivateCharacter(); + + // TODO Delete Equipment + // Resend Character Select Screen. + this.sendCharacterSelectScreen(session); + + } catch (Exception e) { + Logger.error(e); + } + } + + private void RequestGameServer(GameServerIPRequestMsg gameServerIPRequestMessage, ClientConnection conn) { + + Session session; + PlayerCharacter player; + + session = SessionManager.getSession(conn); + player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, gameServerIPRequestMessage.getCharacterUUID()); + + if (player == null) { + Logger.info("Unable to find character ID " + gameServerIPRequestMessage.getCharacterUUID()); + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "PlayerCharacter lookup failed in .RequestGameServer().", conn); + return; + } + + try { + if (!CSSession.updateCrossServerSession(ByteUtils.byteArrayToSafeStringHex(conn.getSecretKeyBytes()), gameServerIPRequestMessage.getCharacterUUID())) { + Logger.info("Failed to update Cross server session, Kicking to Login for Character " + player.getObjectUUID()); + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Failed to update Session Information", conn); + return; + } + } catch (Exception e) { + Logger.info("Failed to update Cross server session, Kicking to Login for Character " + player.getObjectUUID()); + Logger.error(e); + } + + // Set the last character used. + Account account = session.getAccount(); + account.setLastCharIDUsed(gameServerIPRequestMessage.getCharacterUUID()); + + GameServerIPResponseMsg gameServerIPResponseMsg = new GameServerIPResponseMsg(); + + if (!conn.sendMsg(gameServerIPResponseMsg)) { + Logger.error("Failed to send message"); + this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send GameServerIPResponseMsg to client.", conn); + } + } +} diff --git a/src/engine/server/world/WorldServer.java b/src/engine/server/world/WorldServer.java new file mode 100644 index 00000000..eafae89b --- /dev/null +++ b/src/engine/server/world/WorldServer.java @@ -0,0 +1,869 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.server.world; + +import engine.Enum; +import engine.Enum.BuildingGroup; +import engine.Enum.DispatchChannel; +import engine.Enum.MinionType; +import engine.Enum.SupportMsgType; +import engine.InterestManagement.HeightMap; +import engine.InterestManagement.RealmMap; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.DataWarehouse; +import engine.exception.MsgSendException; +import engine.gameManager.*; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.LogoutCharacterJob; +import engine.mobileAI.Threads.MobAIThread; +import engine.mobileAI.Threads.MobRespawnThread; +import engine.net.Dispatch; +import engine.net.DispatchMessage; +import engine.net.ItemProductionManager; +import engine.net.Network; +import engine.net.client.ClientConnection; +import engine.net.client.ClientConnectionManager; +import engine.net.client.ClientMessagePump; +import engine.net.client.Protocol; +import engine.net.client.msg.RefinerScreenMsg; +import engine.net.client.msg.TrainerInfoMsg; +import engine.net.client.msg.UpdateStateMsg; +import engine.net.client.msg.chat.ChatSystemMsg; +import engine.objects.*; +import engine.server.MBServerStatics; +import engine.util.ThreadUtils; +import engine.workthreads.DisconnectTrashTask; +import engine.workthreads.HourlyJobThread; +import engine.workthreads.PurgeOprhans; +import engine.workthreads.WarehousePushThread; +import org.pmw.tinylog.Configurator; +import org.pmw.tinylog.Level; +import org.pmw.tinylog.Logger; +import org.pmw.tinylog.labelers.TimestampLabeler; +import org.pmw.tinylog.policies.StartupPolicy; +import org.pmw.tinylog.writers.RollingFileWriter; + +import java.io.*; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; + +import static engine.gameManager.SimulationManager.SERVERHEARTBEAT; +import static java.lang.System.exit; + +public class WorldServer { + + public static int worldMapID = Integer.parseInt(ConfigManager.MB_WORLD_MAPID.getValue()); + public static int worldRealmMap = Integer.parseInt(ConfigManager.MB_WORLD_REALMMAP.getValue()); + + public static int worldUUID = 1; // Root object in database + public static Enum.AccountStatus worldAccessLevel = Enum.AccountStatus.valueOf(ConfigManager.MB_WORLD_ACCESS_LVL.getValue()); + private static LocalDateTime bootTime = LocalDateTime.now(); + public boolean isRunning = false; + + // Member variable declaration + + public WorldServer() { + super(); + } + + public static void main(String[] args) { + + WorldServer worldServer; + + // Configure TinyLogger + Configurator.defaultConfig() + .addWriter(new RollingFileWriter("logs/world/world.txt", 30, new TimestampLabeler(), new StartupPolicy())) + .level(Level.DEBUG) + .formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}") + .writingThread("main", 2) + .activate(); + + if (ConfigManager.init() == false) { + Logger.error("ABORT! Missing config entry!"); + return; + } + + try { + + worldServer = new WorldServer(); + + ConfigManager.serverType = Enum.ServerType.WORLDSERVER; + ConfigManager.worldServer = worldServer; + ConfigManager.handler = new ClientMessagePump(worldServer); + + worldServer.init(); + + int retVal = worldServer.exec(); + + if (retVal != 0) + Logger.error( + ".exec() returned value: '" + retVal); + exit(retVal); + + } catch (Exception e) { + Logger.error(e.getMessage()); + exit(1); + } + } + + public static void trainerInfo(TrainerInfoMsg msg, ClientConnection origin) { + + NPC npc = NPC.getFromCache(msg.getObjectID()); + float sellPercent = 1; + + if (npc != null) { + + if (origin.getPlayerCharacter() != null) + sellPercent = npc.getSellPercent(origin.getPlayerCharacter()); + else + sellPercent = npc.getSellPercent(); + + msg.setTrainPercent(sellPercent); //TrainMsg.getTrainPercent(npc)); + } + + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + + } + + public static void refinerScreen(RefinerScreenMsg msg, ClientConnection origin) + throws MsgSendException { + + NPC npc = NPC.getFromCache(msg.getNpcID()); + + if (npc != null) + msg.setUnknown02(0); //cost to refine? + + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg); + DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); + } + + public static String getUptimeString() { + + String outString = null; + java.time.Duration uptimeDuration; + String newLine = System.getProperty("line.separator"); + + try { + outString = "[LUA_UPTIME()]" + newLine; + uptimeDuration = java.time.Duration.between(LocalDateTime.now(), WorldServer.bootTime); + long uptimeSeconds = Math.abs(uptimeDuration.getSeconds()); + String uptime = String.format("%d hours %02d minutes %02d seconds", uptimeSeconds / 3600, (uptimeSeconds % 3600) / 60, (uptimeSeconds % 60)); + outString += "uptime: " + uptime; + outString += " pop: " + SessionManager.getActivePlayerCharacterCount() + " max pop: " + SessionManager._maxPopulation; + } catch (Exception e) { + Logger.error("Failed to build string"); + } + return outString; + } + + public static void writePopulationFile() { + + int population = SessionManager.getActivePlayerCharacterCount(); + try { + + + File populationFile = new File(ConfigManager.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'", "") + ".pop"); + FileWriter fileWriter; + + try { + fileWriter = new FileWriter(populationFile, false); + fileWriter.write(Integer.toString(population)); + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } catch (Exception e) { + Logger.error(e); + } + } + + private int exec() { + + LocalDateTime nextHeartbeatTime = LocalDateTime.now(); + LocalDateTime nextPopulationFileTime = LocalDateTime.now(); + LocalDateTime nextFlashTrashCheckTime = LocalDateTime.now(); + LocalDateTime nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1); + LocalDateTime nextWareHousePushTime = LocalDateTime.now(); + + // Begin execution of main game loop + + this.isRunning = true; + + while (true) { + + if (LocalDateTime.now().isAfter(nextHeartbeatTime)) { + SERVERHEARTBEAT.tick(); + nextHeartbeatTime = LocalDateTime.now().plusNanos(50000000); + } + + if (LocalDateTime.now().isAfter(nextPopulationFileTime)) { + writePopulationFile(); + nextPopulationFileTime = LocalDateTime.now().plusMinutes(1); + } + + if (LocalDateTime.now().isAfter(nextFlashTrashCheckTime)) { + processFlashFile(); + processTrashFile(); + nextFlashTrashCheckTime = LocalDateTime.now().plusSeconds(15); + } + + if (LocalDateTime.now().isAfter(nextHourlyJobTime)) { + Thread hourlyJobThread = new Thread(new HourlyJobThread()); + hourlyJobThread.setName("hourlyJob"); + hourlyJobThread.start(); + nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1); + } + + if (LocalDateTime.now().isAfter(nextWareHousePushTime)) { + Thread warehousePushThread = new Thread(new WarehousePushThread()); + warehousePushThread.setName("warehousePush"); + warehousePushThread.start(); + nextWareHousePushTime = LocalDateTime.now().plusMinutes(15); + } + + ThreadUtils.sleep(50); + } + } + + private void initClientConnectionManager() { + + try { + + String name = ConfigManager.MB_WORLD_NAME.getValue(); + + if (ConfigManager.MB_EXTERNAL_ADDR.getValue().equals("0.0.0.0")) { + + // Autoconfigure External IP address. Only used in loginserver but useful + // here for bootstrap display + + Logger.info("AUTOCONFIG EXTERNAL IP ADDRESS"); + URL whatismyip = new URL("http://checkip.amazonaws.com"); + + BufferedReader in = new BufferedReader(new InputStreamReader( + whatismyip.openStream())); + ConfigManager.MB_EXTERNAL_ADDR.setValue(in.readLine()); + } + + if (ConfigManager.MB_BIND_ADDR.getValue().equals("0.0.0.0")) { + + try (final DatagramSocket socket = new DatagramSocket()) { + socket.connect(InetAddress.getByName("8.8.8.8"), 10002); + ConfigManager.MB_BIND_ADDR.setValue(socket.getLocalAddress().getHostAddress()); + } + + } + + Logger.info("External address: " + ConfigManager.MB_EXTERNAL_ADDR.getValue() + ":" + ConfigManager.MB_WORLD_PORT.getValue()); + Logger.info("Internal address: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_WORLD_PORT.getValue()); + + InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue()); + int port = Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue()); + + ClientConnectionManager connectionManager = new ClientConnectionManager(name + ".ClientConnMan", addy, + port); + connectionManager.startup(); + + } catch (IOException e) { + Logger.error("Exception while creating a ClientConnectionManager."); + } + } + + private boolean init() { + + Logger.info("MAGICBANE SERVER GREETING:"); + Logger.info(ConfigManager.MB_WORLD_GREETING.getValue()); + + Logger.info("Initialize network protocol"); + Protocol.initProtocolLookup(); + + Logger.info("Initialize database layer"); + initDatabaselayer(); + + Logger.info("Starting network Dispatcher"); + DispatchMessage.startMessagePump(); + + Logger.info("Setting cross server session behavior"); + SessionManager.setCrossServerBehavior(1); // Sets cross server behavior + + Logger.info("Starting Item Production thread"); + ItemProductionManager.ITEMPRODUCTIONMANAGER.startMessagePump(); + + Logger.info("Initializing Errant Guild"); + Guild.getErrantGuild(); + + Logger.info("Initializing PowersManager."); + PowersManager.initPowersManager(true); + + Logger.info("Initializing granted Skills for Runes"); + DbManager.SkillsBaseQueries.LOAD_ALL_RUNE_SKILLS(); + + Logger.info("Initializing Player Friends"); + DbManager.PlayerCharacterQueries.LOAD_PLAYER_FRIENDS(); + + Logger.info("Initializing NPC Profits"); + DbManager.NPCQueries.LOAD_NPC_PROFITS(); + + Logger.info("Initializing Petition Table"); + DbManager.PetitionQueries.CREATE_PETITION_TABLE(); + + Logger.info("Initializing MeshBounds"); + MeshBounds.InitializeBuildingBounds(); + + Logger.info("Loading ItemBases"); + ItemBase.loadAllItemBases(); + + Logger.info("Loading PromotionClasses"); + DbManager.PromotionQueries.GET_ALL_PROMOTIONS(); + + Logger.info("Loading NPC and Mob Rune Sets"); + NPCManager.LoadAllRuneSets(); + + Logger.info("Loading Booty Sets"); + LootManager._bootySetMap = DbManager.LootQueries.LOAD_BOOTY_TABLES(); + + // Load new loot system + Logger.info("Initializing Loot Manager"); + LootManager.init(); + + RuneBaseAttribute.LoadAllAttributes(); + RuneBase.LoadAllRuneBases(); + BaseClass.LoadAllBaseClasses(); + Race.loadAllRaces(); + RuneBaseEffect.LoadRuneBaseEffects(); + Logger.info("Loading MobBases."); + DbManager.MobBaseQueries.GET_ALL_MOBBASES(); + + Logger.info("Loading Mob Powers"); + PowersManager.AllMobPowers = DbManager.PowerQueries.LOAD_MOB_POWERS(); + + Logger.info("Loading item enchants"); + DbManager.LootQueries.LOAD_ENCHANT_VALUES(); + + Logger.info("Loading zone extent cache"); + DbManager.ZoneQueries.LOAD_ZONE_EXTENTS(); + + Logger.info("Loading Realms"); + Realm.loadAllRealms(); + + Logger.info("Loading RealmMap"); + RealmMap.loadRealmImageMap(); + + Logger.info("Loading Kits"); + DbManager.KitQueries.GET_ALL_KITS(); + + Logger.info("Loading World Grid"); + WorldGrid.InitializeGridObjects(); + + Logger.info("Starting InterestManager."); + WorldGrid.startLoadJob(); + + Logger.info("Loading blueprint data."); + StaticColliders.loadAllStaticColliders(); + BuildingRegions.loadAllStaticColliders(); + Blueprint.loadAllDoorNumbers(); + Blueprint.loadAllBlueprints(); + + Logger.info("Initializing Heightmap data"); + HeightMap.loadAlHeightMaps(); + + Logger.info("Loading Race data"); + Enum.RaceType.initRaceTypeTables(); + Race.loadAllRaces(); + + Logger.info("Loading building slot/stuck location data."); + BuildingLocation.loadBuildingLocations(); + + // Starting before loading of structures/guilds/characters + // so the database connections are available to write + // historical data. + + Logger.info("Starting Data Warehouse"); + DataWarehouse.bootStrap(); + + Logger.info("Loading Minion Bases."); + MinionType.InitializeMinions(); + + Logger.info("Loading Pirate Names."); + NPCManager.loadAllPirateNames(); + + Logger.info("Loading Support Types"); + SupportMsgType.InitializeSupportMsgType(); + + //Load Buildings, Mobs and NPCs for server + + getWorldBuildingsMobsNPCs(); + + // Configure realms for serialization + // Doing this after the world is loaded + + Logger.info("Configuring realm serialization data"); + Realm.configureAllRealms(); + + Logger.info("Loading Mine data."); + Mine.loadAllMines(); + + Logger.info("Loading Shrine data."); + DbManager.ShrineQueries.LOAD_ALL_SHRINES(); + + Logger.info("Initialize Resource type lookup"); + Enum.ResourceType.InitializeResourceTypes(); + + Logger.info("Loading Warehouse data."); + DbManager.WarehouseQueries.LOAD_ALL_WAREHOUSES(); + + Logger.info("Loading Runegate data."); + Runegate.loadAllRunegates(); + + Logger.info("Loading Max Skills for Trainers"); + DbManager.SkillsBaseQueries.LOAD_ALL_MAX_SKILLS_FOR_CONTRACT(); + + //pick a startup Hotzone + ZoneManager.generateAndSetRandomHotzone(); + + Logger.info("Loading All Players from database to Server Cache"); + long start = System.currentTimeMillis(); + + try { + DbManager.PlayerCharacterQueries.GET_ALL_CHARACTERS(); + } catch (Exception e) { + e.printStackTrace(); + } + + long end = System.currentTimeMillis(); + + Logger.info("Loading All Players took " + (end - start) + " ms."); + + ItemProductionManager.ITEMPRODUCTIONMANAGER.initialize(); + + Logger.info("Loading Player Heraldries"); + DbManager.PlayerCharacterQueries.LOAD_HERALDY(); + + Logger.info("Running Heraldry Audit for Deleted Players"); + Heraldry.AuditHeraldry(); + + //intiate mob ai thread + Logger.info("Starting Mob AI Thread"); + MobAIThread.startAIThread(); + + for (Zone zone : ZoneManager.getAllZones()) { + if (zone.getHeightMap() != null) { + if (zone.getHeightMap().getBucketWidthX() == 0) { + System.out.println("Zone load num: " + zone.getLoadNum() + " has no bucket width"); + } + } + } + + Logger.info("World data loaded."); + + //set default accesslevel for server *** Refactor who two separate variables? + MBServerStatics.accessLevel = worldAccessLevel; + Logger.info("Default access level set to " + MBServerStatics.accessLevel); + + Logger.info("Initializing Network"); + Network.init(); + + Logger.info("Initializing Client Connection Manager"); + initClientConnectionManager(); + + //intiate mob respawn thread + Logger.info("Starting Mob Respawn Thread"); + MobRespawnThread.startRespawnThread(); + + // Run maintenance + + MaintenanceManager.dailyMaintenance(); + + Logger.info("Starting Orphan Item Purge"); + PurgeOprhans.startPurgeThread(); + + // Open/Close mines for the current window + + Logger.info("Processing mine window."); + HourlyJobThread.processMineWindow(); + + // Calculate bootstrap time and rest boot time to current time. + + Duration bootDuration = Duration.between(LocalDateTime.now(), bootTime); + long bootSeconds = Math.abs(bootDuration.getSeconds()); + String boottime = String.format("%d hours %02d minutes %02d seconds", bootSeconds / 3600, (bootSeconds % 3600) / 60, (bootSeconds % 60)); + Logger.info("Bootstrap time was " + boottime); + + bootTime = LocalDateTime.now(); + + Logger.info("Running garbage collection..."); + System.gc(); + return true; + } + + protected boolean initDatabaselayer() { + + // Try starting a GOM <-> DB connection. + try { + + Logger.info("Configuring GameObjectManager to use Database: '" + + ConfigManager.MB_DATABASE_NAME.getValue() + "' on " + + ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':' + + ConfigManager.MB_DATABASE_PORT.getValue()); + + DbManager.configureConnectionPool(); + + } catch (Exception e) { + Logger.error(e.getMessage()); + return false; + } + + PreparedStatementShared.submitPreparedStatementsCleaningJob(); + + if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) { + PreparedStatementShared.enableDebugging(); + } + + return true; + } + + private void getWorldBuildingsMobsNPCs() { + + ArrayList rootParent; + + rootParent = DbManager.ZoneQueries.GET_MAP_NODES(worldUUID); + + if (rootParent.isEmpty()) { + Logger.error("populateWorldBuildings: No entries found in worldMap for parent " + worldUUID); + return; + } + + //Set sea floor object for server + Zone seaFloor = rootParent.get(0); + seaFloor.setParent(null); + ZoneManager.setSeaFloor(seaFloor); + + // zoneManager.addZone(seaFloor.getLoadNum(), seaFloor); <- DIE IN A FUCKING CAR FIRE BONUS CODE LIKE THIS SUCKS FUCKING DICK + + rootParent.addAll(DbManager.ZoneQueries.GET_ALL_NODES(seaFloor)); + + long start = System.currentTimeMillis(); + + for (Zone zone : rootParent) { + + try { + ZoneManager.addZone(zone.getLoadNum(), zone); + + try { + zone.generateWorldAltitude(); + } catch (Exception e) { + Logger.error(e.getMessage()); + e.printStackTrace(); + } + + //Handle Buildings + + ArrayList bList; + bList = DbManager.BuildingQueries.GET_ALL_BUILDINGS_FOR_ZONE(zone); + + for (Building b : bList) { + + try { + b.setObjectTypeMask(MBServerStatics.MASK_BUILDING); + b.setLoc(b.getLoc()); + } catch (Exception e) { + Logger.error(b.getObjectUUID() + " returned an Error Message :" + e.getMessage()); + } + } + + //Handle Mobs + ArrayList mobs; + mobs = DbManager.MobQueries.GET_ALL_MOBS_FOR_ZONE(zone); + + for (Mob m : mobs) { + m.setObjectTypeMask(MBServerStatics.MASK_MOB | m.getTypeMasks()); + m.setLoc(m.getLoc()); + + //ADD GUARDS HERE. + if (m.building != null && m.building.getBlueprint() != null && m.building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) + DbManager.MobQueries.LOAD_PATROL_POINTS(m); + } + + //Handle npc's + ArrayList npcs; + + // Ignore npc's on the seafloor (npc guild leaders, etc) + + if (zone.equals(seaFloor)) + continue; + + npcs = DbManager.NPCQueries.GET_ALL_NPCS_FOR_ZONE(zone); + + for (NPC n : npcs) { + + try { + n.setObjectTypeMask(MBServerStatics.MASK_NPC); + n.setLoc(n.getLoc()); + } catch (Exception e) { + Logger.error(n.getObjectUUID() + " returned an Error Message :" + e.getMessage()); + } + } + + //Handle cities + + ZoneManager.loadCities(zone); + ZoneManager.populateWorldZones(zone); + + } catch (Exception e) { + Logger.info(e.getMessage() + zone.getName() + ' ' + zone.getObjectUUID()); + } + } + + Logger.info("time to load: " + (System.currentTimeMillis() - start) + " ms"); + } + + /** + * Called to remove a client on "leave world", "quit game", killed client + * process, etc. + */ + + public void removeClient(ClientConnection origin) { + + if (origin == null) { + Logger.info( + "ClientConnection null in removeClient."); + return; + } + + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter( + origin); + + if (playerCharacter == null) + // TODO log this + return; + + //cancel any trade + if (playerCharacter.getCharItemManager() != null) + playerCharacter.getCharItemManager().endTrade(true); + + // Release any mine claims + + Mine.releaseMineClaims(playerCharacter); + + // logout + long delta = MBServerStatics.LOGOUT_TIMER_MS; + + if (System.currentTimeMillis() - playerCharacter.getTimeStamp("LastCombatPlayer") < 60000) { + delta = 60000; + + } + playerCharacter.stopMovement(playerCharacter.getLoc()); + UpdateStateMsg updateStateMsg = new UpdateStateMsg(); + updateStateMsg.setPlayer(playerCharacter); + + updateStateMsg.setActivity(5); + DispatchMessage.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + + if (playerCharacter.region != null) + if (PlayerCharacter.CanBindToBuilding(playerCharacter, playerCharacter.region.parentBuildingID)) + playerCharacter.bindBuilding = playerCharacter.region.parentBuildingID; + else + playerCharacter.bindBuilding = 0; + + playerCharacter.getLoadedObjects().clear(); + playerCharacter.getLoadedStaticObjects().clear(); + + LogoutCharacterJob logoutJob = new LogoutCharacterJob(playerCharacter, this); + JobContainer jc = JobScheduler.getInstance().scheduleJob(logoutJob, + System.currentTimeMillis() + delta); + playerCharacter.getTimers().put("Logout", jc); + playerCharacter.getTimestamps().put("logout", System.currentTimeMillis()); + + //send update to friends that you are logged off. + + PlayerFriends.SendFriendsStatus(playerCharacter, false); + + } + + public void logoutCharacter(PlayerCharacter player) { + + if (player == null) { + Logger.error("Unable to find PlayerCharacter to logout"); + return; + } + //remove player from loaded mobs agro maps + for(AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(player.getLoc(),MBServerStatics.CHARACTER_LOAD_RANGE,MBServerStatics.MASK_MOB)) { + Mob loadedMob = (Mob) awo; + loadedMob.playerAgroMap.remove(player.getObjectUUID()); + } + player.getTimestamps().put("logout", System.currentTimeMillis()); + player.setEnteredWorld(false); + + // remove from simulation and zero current loc + + WorldGrid.RemoveWorldObject(player); + + // clear Logout Timer + + if (player.getTimers() != null) + player.getTimers().remove("Logout"); + + if (player.getPet() != null) + player.getPet().dismiss(); + + NPCManager.dismissNecroPets(player); + + // Set player inactive so they quit loading for other players + + player.setActive(false); + + // Remove from group + + Group group = GroupManager.getGroup(player); + + try { + if (group != null) + GroupManager.LeaveGroup(player); + } catch (MsgSendException e) { + Logger.error(e.toString()); + } + + player.respawnLock.writeLock().lock(); + try { + if (!player.isAlive()) + player.respawn(false, false, true); + } catch (Exception e) { + Logger.error(e); + } finally { + player.respawnLock.writeLock().unlock(); + } + } + + private void processTrashFile() { + + ArrayList machineList; + ArrayList trashList = new ArrayList<>(); + ArrayList accountList = new ArrayList<>(); + + File trashFile = new File("trash"); + + if (trashFile.exists() == false) + return; + + // Build list of machineID's in the trash file + + machineList = DbManager.AccountQueries.GET_TRASH_LIST(); + + // Build list of trash characters associated with that machineID + + for (String machineID : machineList) { + trashList = DbManager.AccountQueries.GET_ALL_CHARS_FOR_MACHINE(machineID); + + + // Deactivate these players and add them to loginCache table + + for (PlayerCharacter trashPlayer : trashList) { + + if (trashPlayer == null) + continue; + + // Need to collate accounts. + + if (!accountList.contains(trashPlayer.getAccount().getObjectUUID())) + accountList.add(trashPlayer.getAccount().getObjectUUID()); + + DbManager.PlayerCharacterQueries.SET_ACTIVE(trashPlayer, false); + DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(trashPlayer.getObjectUUID(), "character"); + } + } + + // delete vault of associated accounts and then invalidate them + // in the login cache. + + for (Integer accountID : accountList) { + DbManager.AccountQueries.DELETE_VAULT_FOR_ACCOUNT(accountID); + DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(accountID, "account"); + } + + // Trigger the Login Server to invalidate these accounts in the cache.. + + try { + Files.write(Paths.get("cacheInvalid"), "".getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + + // If any of these players are active disconnect them. + // The account and player should be removed from the login + // server cache file by now. + + Timer timer = new Timer("Disconnect Trash"); + timer.schedule(new DisconnectTrashTask(trashList), 3000L); + + // Clean up after ourselves + + try { + Files.deleteIfExists(Paths.get("trash")); + DbManager.AccountQueries.CLEAR_TRASH_TABLE(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private void processFlashFile() { + + File flashFile = new File("flash"); + String flashString; + List fileContents; + + if (flashFile.exists() == false) + return; + + try { + fileContents = Files.readAllLines(Paths.get("flash")); + } catch (IOException e) { + return; + } + + // Flash file detected: read contents + // and send as a flash. + + flashString = fileContents.toString(); + + if (flashString == null) + return; + + if (flashString == "") + flashString = "Rebooting for to fix bug."; + + Logger.info("Sending flash from external interface"); + Logger.info("Msg: " + flashString); + + ChatSystemMsg msg = new ChatSystemMsg(null, flashString); + msg.setChannel(engine.Enum.ChatChannelType.FLASH.getChannelID()); + msg.setMessageType(Enum.ChatMessageType.INFO.ordinal()); + DispatchMessage.dispatchMsgToAll(msg); + + // Delete file + + try { + Files.deleteIfExists(Paths.get("flash")); + } catch (IOException e) { + e.printStackTrace(); + } + + } +} \ No newline at end of file diff --git a/src/engine/session/CSSession.java b/src/engine/session/CSSession.java new file mode 100644 index 00000000..675f394c --- /dev/null +++ b/src/engine/session/CSSession.java @@ -0,0 +1,124 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.session; + +import engine.gameManager.DbManager; +import engine.objects.AbstractGameObject; +import engine.objects.Account; +import engine.objects.PlayerCharacter; + +import java.net.InetAddress; + + +public class CSSession extends AbstractGameObject { + + private String sessionID; + private PlayerCharacter playerCharacter; + private Account account; + + + private String machineID; + + public CSSession(String sessionID, Account acc, PlayerCharacter pc, String machineID) { + super(); + this.sessionID = sessionID; + this.playerCharacter = pc; + this.account = acc; + this.machineID = machineID; + + if (this.playerCharacter != null) + PlayerCharacter.initializePlayer(this.playerCharacter); + } + + /* + * Database + */ + public static boolean addCrossServerSession(String secKey, Account acc, InetAddress inet, String machineID) { + return DbManager.CSSessionQueries.ADD_CSSESSION(secKey, acc, inet, machineID); + // PreparedStatementShared ps = null; + // try { + // ps = prepareStatement("INSERT INTO sessions (secretKey, accountID, vbID, sessionIP) VALUES (?,?,?,INET_ATON(?))"); + // ps.setString(1, secKey); + // ps.setInt(2, acc.getUUID(), true); + // ps.setInt(3, acc.getVBID()); + // ps.setString(4, StringUtils.InetAddressToClientString(inet)); + // if (ps.executeUpdate() > 0) + // return true; + // } catch (SQLException e) { + // Logger.error("CSSession", "Failed to create cross server session"); + // } finally { + // ps.release(); + // } + // return false; + } + + public static boolean deleteCrossServerSession(String secKey) { + return DbManager.CSSessionQueries.DELETE_CSSESSION(secKey); + // PreparedStatementShared ps = null; + // try { + // ps = prepareStatement("DELETE FROM sessions WHERE secretKey = ?"); + // ps.setString(1, secKey); + // if (ps.executeUpdate() > 0) + // return true; + // } catch (SQLException e) { + // Logger.error("CSSession", "Failed to delete cross server session"); + // } finally { + // ps.release(); + // } + // return false; + } + + public static boolean updateCrossServerSession(String secKey, int charID) { + return DbManager.CSSessionQueries.UPDATE_CSSESSION(secKey, charID); + } + + public static CSSession getCrossServerSession(String secKey) { + + CSSession sessionInfo; + + try { + sessionInfo = DbManager.CSSessionQueries.GET_CSSESSION(secKey); + } catch (Exception e) { + sessionInfo = null; + } + + return sessionInfo; + } + + public PlayerCharacter getPlayerCharacter() { + return this.playerCharacter; + } + + public void setPlayerCharacter(PlayerCharacter pc) { + this.playerCharacter = pc; + } + + public Account getAccount() { + return this.account; + } + + public void setAccount(Account acc) { + this.account = acc; + } + + @Override + public void removeFromCache() { + } + + public String getMachineID() { + return machineID; + } + + @Override + public void updateDatabase() { + // TODO Auto-generated method stub + + } +} diff --git a/src/engine/session/Session.java b/src/engine/session/Session.java new file mode 100644 index 00000000..8a8de33c --- /dev/null +++ b/src/engine/session/Session.java @@ -0,0 +1,71 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.session; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.net.client.ClientConnection; +import engine.objects.Account; +import engine.objects.PlayerCharacter; + + +public class Session { + + private SessionID sessionID; + private PlayerCharacter playerCharacter; + private Account account; + private ClientConnection conn; + + public Session(SessionID sessionID, Account acc, ClientConnection conn) { + super(); + this.sessionID = sessionID; + this.playerCharacter = null; + this.account = acc; + this.conn = conn; + } + + public PlayerCharacter getPlayerCharacter() { + if (this.playerCharacter != null) { + + if (DbManager.inCache(Enum.GameObjectType.PlayerCharacter, this.playerCharacter.getObjectUUID())) + this.playerCharacter = (PlayerCharacter) DbManager.getFromCache(Enum.GameObjectType.PlayerCharacter, this.playerCharacter.getObjectUUID()); + } + return this.playerCharacter; + } + + public void setPlayerCharacter(PlayerCharacter pc) { + this.playerCharacter = pc; + } + + public SessionID getSessionID() { + return this.sessionID; + } + + public void setSessionID(SessionID sessionID) { + this.sessionID = sessionID; + } + + public Account getAccount() { + return this.account; + } + + public void setAccount(Account acc) { + this.account = acc; + } + + public ClientConnection getConn() { + return this.conn; + } + + public void setConn(ClientConnection conn) { + this.conn = conn; + } + +} diff --git a/src/engine/session/SessionID.java b/src/engine/session/SessionID.java new file mode 100644 index 00000000..802d831c --- /dev/null +++ b/src/engine/session/SessionID.java @@ -0,0 +1,50 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.session; + +import engine.util.ByteUtils; + +public class SessionID { + + private final byte[] data; + private final String dataAsString; + + public SessionID(byte[] data) { + super(); + this.data = data; + this.dataAsString = ByteUtils.byteArrayToStringHex(data); + } + + @Override + public boolean equals(Object obj) { + boolean out = false; + if (obj instanceof SessionID) { + out = true; + SessionID id = (SessionID) obj; + for (int i = 0; out && i < id.data.length; ++i) { + if (id.data[i] != this.data[i]) { + out = false; + } + } + } + + return out; + } + + @Override + public String toString() { + return this.dataAsString; + } + + public final byte[] getData() { + return data; + } + +} diff --git a/src/engine/util/ByteAnalyzer.java b/src/engine/util/ByteAnalyzer.java new file mode 100644 index 00000000..3f84444d --- /dev/null +++ b/src/engine/util/ByteAnalyzer.java @@ -0,0 +1,224 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.util; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; + +public class ByteAnalyzer { + + public static void analyze4Bytes(byte[] data, String Label, + boolean switchEndian) throws IOException { + + ByteArrayInputStream bias = new ByteArrayInputStream(data); + DataInputStream dis = new DataInputStream(bias); + dis.mark(4); + + System.out.println("Analysis 4 bytes: (" + Label + ')'); + System.out.println("\t Hex: " + ByteUtils.byteArrayToStringHex(data)); + System.out.println(ByteAnalyzer.buildAll4(dis, switchEndian)); + System.out.println(ByteAnalyzer.buildUTF8(dis)); + System.out.println(ByteAnalyzer.buildUTF16(dis)); + System.out.println(ByteAnalyzer.buildRawNumericalBytes(dis)); + System.out.println(); + } + + public static void analyze8Bytes(byte[] data, String Label, + boolean switchEndian) throws IOException { + + ByteArrayInputStream bias = new ByteArrayInputStream(data); + DataInputStream dis = new DataInputStream(bias); + dis.mark(8); + + System.out.println("Analysis for 8 bytes: (" + Label + ')'); + System.out.println("\tHex: " + ByteUtils.byteArrayToStringHex(data)); + System.out.println(ByteAnalyzer.buildAll8(dis, switchEndian)); + dis.reset(); + System.out.println(ByteAnalyzer.buildUTF8(dis)); + System.out.println(ByteAnalyzer.buildUTF16(dis)); + System.out.println(ByteAnalyzer.buildRawNumericalBytes(dis)); + System.out.println("\n"); + } + + public static String buildAll8(DataInputStream indis, boolean se) + throws IOException { + byte[] ba = new byte[8]; + indis.read(ba); + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(ba)); + dis.mark(8); + + String out = ""; + + out += buildFromTemplate("8", dis, se); + out += buildFromTemplate("4.4", dis, se); + + out += buildFromTemplate("4.2.2", dis, se); + out += buildFromTemplate("4.2.1.1", dis, se); + out += buildFromTemplate("4.1.2.1", dis, se); + out += buildFromTemplate("4.1.1.2", dis, se); + out += buildFromTemplate("4.1.1.1.1", dis, se); + + out += buildFromTemplate("2.2.4", dis, se); + out += buildFromTemplate("2.1.1.4", dis, se); + out += buildFromTemplate("1.2.1.4", dis, se); + out += buildFromTemplate("1.1.2.4", dis, se); + out += buildFromTemplate("1.1.1.1.4", dis, se); + + out += buildFromTemplate("2.4.2", dis, se); + out += buildFromTemplate("2.4.1.1", dis, se); + out += buildFromTemplate("1.1.4.2", dis, se); + out += buildFromTemplate("1.1.4.1.1", dis, se); + + out += buildFromTemplate("2.1.2.2.1", dis, se); + out += buildFromTemplate("2.1.2.1.2", dis, se); + out += buildFromTemplate("1.2.2.2.1", dis, se); + out += buildFromTemplate("1.2.2.1.2", dis, se); + + out += buildFromTemplate("1.1.1.2.2.1", dis, se); + out += buildFromTemplate("2.1.2.1.1.1", dis, se); + out += buildFromTemplate("1.1.1.2.1.1.1", dis, se); + out += buildFromTemplate("1.1.1.1.1.1.1.1", dis, se); + + out += buildFromTemplate("2.1.1.1.1.1.1", dis, se); + out += buildFromTemplate("1.2.1.1.1.1.1", dis, se); + out += buildFromTemplate("1.1.2.1.1.1.1", dis, se); + + out += buildFromTemplate("1.1.1.1.2.1.1", dis, se); + out += buildFromTemplate("1.1.1.1.1.2.1", dis, se); + out += buildFromTemplate("1.1.1.1.1.1.2", dis, se); + + return out; + } + + public static String buildAll4(DataInputStream indis, boolean se) + throws IOException { + byte[] ba = new byte[4]; + indis.read(ba); + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(ba)); + dis.mark(4); + + String out = ""; + out += buildFromTemplate("4", dis, se); + out += buildFromTemplate("2.2", dis, se); + out += buildFromTemplate("2.1.1", dis, se); + out += buildFromTemplate("1.2.1", dis, se); + out += buildFromTemplate("1.1.2", dis, se); + out += buildFromTemplate("1.1.1.1", dis, se); + + return out; + } + + public static String buildFromTemplate(String template, + DataInputStream dis, boolean se) throws IOException { + String out = '\t' + template + ": "; + + for (int i = template.length(); i < 16; ++i) { + out += " "; + } + + template = template.replace(".", ""); + String[] items = template.split(""); + dis.mark(dis.available()); + + for (String s : items) { + if (s.equals("1")) { + out += " (B:" + dis.readByte(); + dis.reset(); + out += "/uB:" + dis.readUnsignedByte() + ')'; + } else if (s.equals("2")) { + byte[] read = new byte[2]; + dis.read(read); + byte[] use = new byte[2]; + if (se) { + use = ByteUtils.switchByteArrayEndianness(read); + } else { + use = read; + } + out += " (S:" + + new DataInputStream(new ByteArrayInputStream(use)) + .readShort(); + out += "/uS:" + + new DataInputStream(new ByteArrayInputStream(use)) + .readUnsignedShort() + ')'; + + } else if (s.equals("4")) { + byte[] read = new byte[4]; + dis.read(read); + byte[] use = new byte[4]; + if (se) { + use = ByteUtils.switchByteArrayEndianness(read); + } else { + use = read; + } + out += " (I:"; + out += new DataInputStream(new ByteArrayInputStream(use)) + .readInt(); + + out += " / F:"; + out += new DataInputStream(new ByteArrayInputStream(use)) + .readFloat() + + ")"; + } else if (s.equals("8")) { + byte[] read = new byte[8]; + dis.read(read); + + byte[] use = new byte[8]; + if (se) { + use = ByteUtils.switchByteArrayEndianness(read); + } else { + use = read; + } + out += " (L:"; + out += new DataInputStream(new ByteArrayInputStream(use)) + .readLong(); + + out += " / D:"; + out += new DataInputStream(new ByteArrayInputStream(use)) + .readDouble() + + ")"; + } + } + dis.reset(); + return out + '\n'; + } + + public static String buildUTF8(DataInputStream dis) throws IOException { + dis.mark(dis.available()); + String out = "\tUTF-8: "; + while (dis.available() > 1) { + out += " '" + (char) dis.read() + '\''; + } + dis.reset(); + return out; + } + + public static String buildUTF16(DataInputStream dis) throws IOException { + dis.mark(dis.available()); + String out = "\tUTF-16:"; + while (dis.available() > 1) { + out += " '" + dis.readChar() + '\''; + } + dis.reset(); + return out; + } + + public static String buildRawNumericalBytes(DataInputStream dis) + throws IOException { + dis.mark(dis.available()); + String out = "\tRaw Bytes (int vals): "; + while (dis.available() > 1) { + out += " '" + dis.read() + '\''; + } + dis.reset(); + return out; + } + +} diff --git a/src/engine/util/ByteBufferUtils.java b/src/engine/util/ByteBufferUtils.java new file mode 100644 index 00000000..e937d327 --- /dev/null +++ b/src/engine/util/ByteBufferUtils.java @@ -0,0 +1,222 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.util; + +import java.io.UnsupportedEncodingException; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; + +public class ByteBufferUtils { + public static String getString(ByteBuffer bb) + throws BufferUnderflowException { + return getString(bb, false, false); + } + + public static String getString(ByteBuffer bb, boolean switchEndian, boolean small) + throws BufferUnderflowException { + String out = ""; + synchronized (bb) { + + //This version works with non-latin characters + int stringLen; + if (small) + stringLen = (int) bb.get(); + else + stringLen = bb.getInt(); + if (switchEndian) + stringLen = ((Integer.reverseBytes(stringLen)) * 2); + else + stringLen *= 2; + byte[] b = new byte[stringLen]; + for (int i = 0; i < stringLen; i += 2) { + if (switchEndian) { + b[i + 1] = bb.get(); + b[i] = bb.get(); + } else { + b[i] = bb.get(); + b[i + 1] = bb.get(); + } + } + try { + out = new String(b, "UTF-16BE"); + } catch (UnsupportedEncodingException e) { + } + } + return out; + } + + public static void putString(ByteBuffer bb, String data, boolean small) + throws BufferOverflowException { + putString(bb, data, false, small); + } + + public static void putString(ByteBuffer bb, String data, + boolean switchEndian, boolean small) throws BufferOverflowException { + if (data == null) { + data = ""; + } + char[] chars = data.toCharArray(); + + int length = chars.length; + if (small && length > 255) + length = 255; //limit for smallString + + synchronized (bb) { + // Write length + if (small) + bb.put((byte) length); + else { + if (switchEndian) { + bb.putInt(Integer.reverseBytes(length)); + } else { + bb.putInt(length); + } + } + // Write chars + for (int i = 0; i < length; i++) { + char c = chars[i]; + if (switchEndian) { + bb.putChar(Character.reverseBytes(c)); + } else { + bb.putChar(c); + } + } + } + } + + public static String getHexString(ByteBuffer bb) + throws BufferUnderflowException { + return getHexString(bb, false); + } + + public static String getHexString(ByteBuffer bb, boolean switchEndian) + throws BufferUnderflowException { + String out = ""; + synchronized (bb) { + // Read len + int stringLen = bb.getInt(); + + if (switchEndian) { + stringLen = Integer.reverseBytes(stringLen); + } + + // Read len worth of chars + for (int i = 0; i < stringLen; ++i) { + out += Integer.toString((bb.get() & 0xff) + 0x100, 16) + .substring(1); + } + } + return out; + } + + public static void putHexString(ByteBuffer bb, String data) + throws BufferOverflowException { + putHexString(bb, data, false); + } + + public static void putHexString(ByteBuffer bb, String data, + boolean switchEndian) throws BufferOverflowException { + + if (data == null) { + data = ""; + } + + byte[] bts = new byte[data.length() / 2]; + + for (int i = 0; i < bts.length; i++) { + bts[i] = (byte) Integer.parseInt(data.substring(2 * i, 2 * i + 1), + 16); + } + + synchronized (bb) { + if (switchEndian) { + bb.putInt(Integer.reverseBytes(data.length() / 2)); + } else { + bb.putInt(data.length() / 2); + } + bb.put(bts); + } + } + + public static String getUnicodeString(ByteBuffer bb) + throws BufferUnderflowException { + return getUnicodeString(bb, false); + } + + public static String getUnicodeString(ByteBuffer bb, boolean switchEndian) + throws BufferUnderflowException { + byte[] out; + short thisChar; + synchronized (bb) { + // Read len + int stringLen = bb.getInt(); + if (switchEndian) { + stringLen = Integer.reverseBytes(stringLen); + } + out = new byte[stringLen]; + // Read len worth of chars + for (int i = 0; i < stringLen; ++i) { + thisChar = bb.getShort(); + if (switchEndian) + Short.reverseBytes(thisChar); + out[i] = (byte) (thisChar & 0xff); // ignore first byte + } + } + return new String(out); + } + + public static void putUnicodeString(ByteBuffer bb, String data) + throws BufferOverflowException { + putUnicodeString(bb, data, false); + } + + public static void putUnicodeString(ByteBuffer bb, String data, + boolean switchEndian) throws BufferOverflowException { + byte[] out; + short thisChar; + if (data == null) + return; + out = new byte[data.length()]; + out = data.getBytes(); + + for (byte b : out) { + thisChar = b; + if (switchEndian) + thisChar = Short.reverseBytes(thisChar); + bb.putShort(thisChar); + } + } + + public static boolean checkByteBufferNearFull(ByteBuffer bb) { + return bb.position() >= (bb.capacity() * 0.9); + } + + //FIXME: Replace these!!! +// public static ByteBuffer resizeByteBuffer(ByteBuffer bb, int multiplyer) { +// +// ByteBuffer out = ByteBuffer.allocate(bb.capacity() * multiplyer); +// +// // Copy the data to a temp buf +// bb.flip(); +// out.put(bb); +// +// return out; +// } +// +// public static ByteBuffer shrinkByteBuffer(ByteBuffer bb) { +// +// bb.flip(); +// ByteBuffer out = ByteBuffer.allocate(bb.remaining()); +// out.put(bb); +// return out; +// } + +} diff --git a/src/engine/util/ByteUtils.java b/src/engine/util/ByteUtils.java new file mode 100644 index 00000000..bcd766c8 --- /dev/null +++ b/src/engine/util/ByteUtils.java @@ -0,0 +1,171 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +public abstract class ByteUtils { + + private ByteUtils() { + } + + public static byte[] switchByteArrayEndianness(byte[] in) { + int size = in.length; + + byte[] out = new byte[size]; + + for (int i = 0; i < size; ++i) { + out[size - i - 1] = in[i]; + } + return out; + } + + /* + * Converts a single byte to a hex StringBuffer + */ + public static void byteToStringHex(byte b, StringBuffer buf) { + char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F'}; + int high = ((b & 0xf0) >> 4); + int low = (b & 0x0f); + buf.append(hexChars[high]); + buf.append(hexChars[low]); + } + + /* + * Converts a single byte to a hex String + */ + public static String byteToStringHex(byte b) { + StringBuffer sb = new StringBuffer(); + byteToStringHex(b, sb); + return sb.toString(); + } + + /* + * Converts a byte array to hex String + */ + public static String byteArrayToStringHex(byte[] block) { + StringBuffer buf = new StringBuffer(); + int len = block.length; + + for (int i = 0; i < len; i++) { + ByteUtils.byteToStringHex(block[i], buf); + if (i < len - 1) { + buf.append(':'); + } + } + return buf.toString(); + } + + /* + * Converts a single byte to a hex StringBuffer + */ + public static void byteToSafeStringHex(byte b, StringBuffer buf) { + char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'}; + int high = ((b & 0xf0) >> 4); + int low = (b & 0x0f); + buf.append(hexChars[high]); + buf.append(hexChars[low]); + } + + /* + * Converts a single byte to a hex String + */ + public static String byteToSafeStringHex(byte b) { + StringBuffer sb = new StringBuffer(); + byteToSafeStringHex(b, sb); + return sb.toString(); + } + + /* + * Converts a byte array to hex String + */ + public static String byteArrayToSafeStringHex(byte[] block) { + StringBuffer buf = new StringBuffer(); + + int len = block.length; + + for (int i = 0; i < len; i++) { + ByteUtils.byteToSafeStringHex(block[i], buf); + } + return buf.toString(); + } + + /* + * Converts a hex string to Byte Array + */ + public static byte[] stringHexToByteArray(String hex) { + int length = hex.length(); + char[] hexchar = hex.toCharArray(); + byte[] ret = new byte[length / 2]; + int i1 = 0; + + for (int i = 0; i < length - 1; i += 2) { + ret[i1] = (byte) (Character.digit(hexchar[i], 16) * 16 + Character + .digit(hexchar[i + 1], 16)); + i1++; + } + + return ret; + } + + /* + * Converts a hex string formatted by our byteToStringHex to a byte array + * returns null if passed a null string + */ + public static byte[] formattedStringHexToByteArray(String hex) { + if (hex == null) { + return null; + } + + String tmpString = hex.replaceAll(":", ""); + int length = tmpString.length(); + char[] hexchar = tmpString.toCharArray(); + byte[] ret = new byte[length / 2]; + int i1 = 0; + + for (int i = 0; i < length - 1; i += 2) { + ret[i1] = (byte) (Character.digit(hexchar[i], 16) * 16 + Character + .digit(hexchar[i + 1], 16)); + i1++; + } + + return ret; + } + + public static byte[] compress(final byte[] in) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final GZIPOutputStream gzOs = new GZIPOutputStream(out); + gzOs.write(in); + gzOs.close(); + return out.toByteArray(); + } + + public static byte[] decompress(final byte[] in) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final GZIPInputStream gzIs = new GZIPInputStream(new ByteArrayInputStream(in)); + final byte[] buffer = new byte[512]; + int lastRead = 0; + + lastRead = gzIs.read(buffer); + while (lastRead > 0) { + out.write(buffer, 0, lastRead); + lastRead = gzIs.read(buffer); + } + gzIs.close(); + return out.toByteArray(); + } + +} diff --git a/src/engine/util/Hasher.java b/src/engine/util/Hasher.java new file mode 100644 index 00000000..ccb60c7c --- /dev/null +++ b/src/engine/util/Hasher.java @@ -0,0 +1,382 @@ +package engine.util; + + +import java.util.*; + +/* + * Author: https://github.com/peet/hashids.java + */ + +public class Hasher { + + private static final String DEFAULT_ALPHABET = "xcS4F6h89aUbideAI7tkynuopqrXCgTE5GBKHLMjfRsz"; + private static final int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43}; + private static final int[] SEPS_INDICES = {0, 4, 8, 12}; + + private String salt_ = ""; + + private String alphabet_ = ""; + + private int minHashLength_; + + private ArrayList seps_ = new ArrayList<>(); + private ArrayList guards_ = new ArrayList<>(); + + public Hasher() { + this(""); + } + + public Hasher(String salt) { + this(salt, 0); + } + + public Hasher(String salt, int minHashLength) { + this(salt, minHashLength, DEFAULT_ALPHABET); + } + + public Hasher(String salt, int minHashLength, String alphabet) { + if (alphabet == null || alphabet.trim().isEmpty()) { + throw new IllegalArgumentException("alphabet must not be empty"); + } + + if (salt != null) { + salt_ = salt; + } + + if (minHashLength > 0) { + minHashLength_ = minHashLength; + } + + alphabet_ = join(new LinkedHashSet<>(Arrays.asList(alphabet.split(""))), ""); + + if (alphabet_.length() < 4) { + throw new IllegalArgumentException("Alphabet must contain at least 4 unique characters."); + } + + for (int prime : PRIMES) { + if (prime < alphabet_.length()) { + char c = alphabet_.charAt(prime - 1); + seps_.add(c); + alphabet_ = alphabet_.replace(c, ' '); + } + } + + for (int index : SEPS_INDICES) { + if (index < seps_.size()) { + guards_.add(seps_.get(index)); + seps_.remove(index); + } + } + + alphabet_ = consistentShuffle(alphabet_.replaceAll(" ", ""), salt_); + } + + private static String hash(long number, String alphabet) { + String hash = ""; + + while (number > 0) { + hash = alphabet.charAt((int) (number % alphabet.length())) + hash; + number /= alphabet.length(); + } + + return hash; + } + + private static long unhash(String hash, String alphabet) { + int number = 0; + + for (int i = 0; i < hash.length(); i++) { + int pos = alphabet.indexOf(hash.charAt(i)); + number += pos * (int) Math.pow(alphabet.length(), hash.length() - i - 1); + } + + return number; + } + + private static String consistentShuffle(String alphabet, String salt) { + String ret = ""; + + if (!alphabet.isEmpty()) { + List alphabetArray = charArrayToStringList(alphabet.toCharArray()); + if (salt == null || salt.isEmpty()) { + salt = new String(new char[]{'\0'}); + } + + int[] sortingArray = new int[salt.length()]; + for (int i = 0; i < salt.length(); i++) { + sortingArray[i] = salt.charAt(i); + } + + for (int i = 0; i < sortingArray.length; i++) { + boolean add = true; + + for (int k = i; k != sortingArray.length + i - 1; k++) { + int nextIndex = (k + 1) % sortingArray.length; + + if (add) { + sortingArray[i] += sortingArray[nextIndex] + (k * i); + } else { + sortingArray[i] -= sortingArray[nextIndex]; + } + + add = !add; + } + + sortingArray[i] = Math.abs(sortingArray[i]); + } + + int i = 0; + while (alphabetArray.size() > 0) { + int pos = sortingArray[i]; + if (pos >= alphabetArray.size()) { + pos %= alphabetArray.size(); + } + ret += alphabetArray.get(pos); + alphabetArray.remove(pos); + i = ++i % sortingArray.length; + } + } + return ret; + } + + public static String getVersion() { + return "0.1.4"; + } + + private static long[] longListToPrimitiveArray(List longs) { + long[] longArr = new long[longs.size()]; + int i = 0; + for (long l : longs) { + longArr[i++] = l; + } + return longArr; + } + + private static List charArrayToStringList(char[] chars) { + ArrayList list = new ArrayList<>(chars.length); + for (char c : chars) { + list.add(String.valueOf(c)); + } + return list; + } + + private static String join(long[] a, String delimiter) { + ArrayList strList = new ArrayList<>(a.length); + for (long l : a) { + strList.add(String.valueOf(l)); + } + return join(strList, delimiter); + } + + private static String join(Collection s, String delimiter) { + Iterator iter = s.iterator(); + if (iter.hasNext()) { + StringBuilder builder = new StringBuilder(s.size()); + builder.append(iter.next()); + while (iter.hasNext()) { + builder.append(delimiter); + builder.append(iter.next()); + } + return builder.toString(); + } + return ""; + } + + public static int SBStringHash(String toHash) { + byte[] hashArray = toHash.getBytes(); + int hash = 0; + int shift = 0; + + if (toHash.equals("SafeModeA")) + return -1661750486; + if (toHash.equals("SafeModeB")) + return -1661751254; + + if (toHash.equals("INVIS-D")) + return -1661751254; + + if (toHash.equals("SafeMode")) + return -1661750486; + + + if ((hashArray.length != 8 && hashArray.length != 7) || hashArray[3] != 45) { + hash = 0; + shift = 0; + for (int i = 0; i < hashArray.length; i++) { + if (i == 0) + shift = 0; + else + shift = shift + 5; + int toShift = hashArray[i] - 0x20; + hash ^= toShift << shift; + + if (shift > 24) { + + + int newShift = 0x20 - shift; + hash ^= toShift >> newShift; + + if (shift >= 27) { + shift = shift - 0x20; + } + } + } + return hash; + } else { + int ecx = 0; + if (hashArray.length == 8) { + ecx = hashArray[7]; + } + int eax = hashArray[4]; + int esi = ecx * 0x8; + eax = eax ^ esi; + ecx = ecx ^ 0x5A0; + esi = hashArray[5]; + eax = eax << 4; + eax = eax ^ esi; + esi = hashArray[6]; + eax = eax << 4; + eax = eax ^ esi; + esi = hashArray[2]; + eax = eax << 5; + eax = eax ^ esi; + esi = hashArray[1]; + int edx = hashArray[0]; + eax = eax << 5; + eax = eax ^ esi; + ecx = ecx / 2; + ecx = ecx / 2; + eax = eax << 5; + ecx = ecx ^ edx; + eax = eax ^ ecx; + return eax; + } + } + + public String encrypt(long... numbers) { + return encode(numbers, alphabet_, salt_, minHashLength_); + } + + public long[] decrypt(String hash) { + return decode(hash); + } + + private String encode(long[] numbers, String alphabet, String salt, int minHashLength) { + String ret = ""; + String seps = consistentShuffle(join(seps_, ""), join(numbers, "")); + char lotteryChar = 0; + + for (int i = 0; i < numbers.length; i++) { + if (i == 0) { + String lotterySalt = join(numbers, "-"); + for (long number : numbers) { + lotterySalt += "-" + (number + 1) * 2; + } + String lottery = consistentShuffle(alphabet, lotterySalt); + lotteryChar = lottery.charAt(0); + ret += lotteryChar; + + alphabet = lotteryChar + alphabet.replaceAll(String.valueOf(lotteryChar), ""); + } + + alphabet = consistentShuffle(alphabet, ((int) lotteryChar & 12345) + salt); + ret += hash(numbers[i], alphabet); + + if (i + 1 < numbers.length) { + ret += seps.charAt((int) ((numbers[i] + i) % seps.length())); + } + } + + if (ret.length() < minHashLength) { + int firstIndex = 0; + for (int i = 0; i < numbers.length; i++) { + firstIndex += (i + 1) * numbers[i]; + } + + int guardIndex = firstIndex % guards_.size(); + char guard = guards_.get(guardIndex); + ret = guard + ret; + + if (ret.length() < minHashLength) { + guardIndex = (guardIndex + ret.length()) % guards_.size(); + guard = guards_.get(guardIndex); + ret += guard; + } + } + + while (ret.length() < minHashLength) { + long[] padArray = new long[]{alphabet.charAt(1), alphabet.charAt(0)}; + String padLeft = encode(padArray, alphabet, salt, 0); + String padRight = encode(padArray, alphabet, join(padArray, ""), 0); + + ret = padLeft + ret + padRight; + int excess = ret.length() - minHashLength; + if (excess > 0) { + ret = ret.substring(excess / 2, excess / 2 + minHashLength); + } + alphabet = consistentShuffle(alphabet, salt + ret); + } + + return ret; + } + + private long[] decode(String hash) { + List ret = new ArrayList<>(); + String originalHash = hash; + + if (hash != null && !hash.isEmpty()) { + String alphabet = ""; + char lotteryChar = 0; + + for (char guard : guards_) { + hash = hash.replaceAll(String.valueOf(guard), " "); + } + + String[] hashSplit = hash.split(" "); + + hash = hashSplit[hashSplit.length == 3 || hashSplit.length == 2 ? 1 : 0]; + + for (char sep : seps_) { + hash = hash.replaceAll(String.valueOf(sep), " "); + } + + String[] hashArray = hash.split(" "); + for (int i = 0; i < hashArray.length; i++) { + String subHash = hashArray[i]; + + if (subHash != null && !subHash.isEmpty()) { + if (i == 0) { + lotteryChar = hash.charAt(0); + subHash = subHash.substring(1); + alphabet = lotteryChar + alphabet_.replaceAll(String.valueOf(lotteryChar), ""); + } + } + + if (alphabet.length() > 0) { + alphabet = consistentShuffle(alphabet, ((int) lotteryChar & 12345) + salt_); + ret.add(unhash(subHash, alphabet)); + } + } + } + + long[] numbers = longListToPrimitiveArray(ret); + + if (!encrypt(numbers).equals(originalHash)) { + return new long[0]; + } + + return numbers; + } + + public String getSalt() { + return salt_; + } + + public String getAlphabet() { + return alphabet_; + } + + public int getMinHashLength() { + return minHashLength_; + } +} \ No newline at end of file diff --git a/src/engine/util/KeyCloneAudit.java b/src/engine/util/KeyCloneAudit.java new file mode 100644 index 00000000..cdae6317 --- /dev/null +++ b/src/engine/util/KeyCloneAudit.java @@ -0,0 +1,30 @@ +package engine.util; + +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.objects.Group; +import engine.objects.PlayerCharacter; +import org.pmw.tinylog.Logger; + +public enum KeyCloneAudit { + KEYCLONEAUDIT; + + public void audit(PlayerCharacter player, Group group) { + + int machineCount = 0; + String machineID; + + machineID = player.getClientConnection().machineID; + + for (PlayerCharacter member : group.getMembers()) + if (machineID.equals(member.getClientConnection().machineID)) + machineCount = machineCount + 1; + + if (machineCount > Integer.parseInt(ConfigManager.MB_WORLD_KEYCLONE_MAX.getValue())) { + Logger.error("Keyclone detected from: " + player.getAccount().getUname() + + " with machine count of: " + machineCount); + DbManager.AccountQueries.SET_TRASH(machineID); + } + + } +} diff --git a/src/engine/util/MapLoader.java b/src/engine/util/MapLoader.java new file mode 100644 index 00000000..a8f14ae6 --- /dev/null +++ b/src/engine/util/MapLoader.java @@ -0,0 +1,90 @@ +/* + * Copyright MagicBane 2013 + */ + +package engine.util; + +import engine.InterestManagement.RealmMap; +import engine.gameManager.ConfigManager; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public enum MapLoader { + + MAPLOADER; + + public static int[][] loadMap() { + + BufferedImage image; + int[][] realmMap; + long timeToLoad = System.currentTimeMillis(); + long bytesRead = 0; + long realmsWritten = 0; + int realmUUID; + String mapPath = null; + + // Load image from disk + + try { + mapPath = ConfigManager.DEFAULT_DATA_DIR + "realmmaps/" + ConfigManager.MB_WORLD_REALMMAP.getValue() + ".png"; + image = ImageIO.read(new File(mapPath)); + + // Array size determined by image size + MBServerStatics.SPATIAL_HASH_BUCKETSX = image.getWidth(); + MBServerStatics.SPATIAL_HASH_BUCKETSY = image.getHeight(); + realmMap = new int[MBServerStatics.SPATIAL_HASH_BUCKETSX][MBServerStatics.SPATIAL_HASH_BUCKETSY]; + } catch (IOException e) { + Logger.error("Error loading realm map: " + mapPath); + return null; + } + + // Flip image on the y axis + + image = flipImage(image); + + // Load spatial imageMap with color data from file + + for (int i = 0; i < MBServerStatics.SPATIAL_HASH_BUCKETSY; i++) { + for (int j = 0; j < MBServerStatics.SPATIAL_HASH_BUCKETSX; j++) { + + Color pixelColor = new Color(image.getRGB(j, i)); + realmUUID = RealmMap.getRealmIDByColor(pixelColor); + + realmMap[j][i] = realmUUID; + bytesRead++; + + if (realmUUID != 0) + realmsWritten++; + + } + } + + timeToLoad = System.currentTimeMillis() - timeToLoad; + + + Logger.info("Realmmap: " + mapPath); + Logger.info(bytesRead + " pixels processed in " + timeToLoad + " milis"); + Logger.info(realmsWritten + " realm pixels written "); + return realmMap; + } + + public static BufferedImage flipImage(BufferedImage img) { + + int w = img.getWidth(); + int h = img.getHeight(); + + BufferedImage dimg = new BufferedImage(w, h, img.getColorModel() + .getTransparency()); + + Graphics2D g = dimg.createGraphics(); + g.drawImage(img, 0, 0, w, h, 0, h, w, 0, null); + g.dispose(); + return dimg; + } +} diff --git a/src/engine/util/MiscUtils.java b/src/engine/util/MiscUtils.java new file mode 100644 index 00000000..9f077c69 --- /dev/null +++ b/src/engine/util/MiscUtils.java @@ -0,0 +1,38 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.util; + +import engine.gameManager.ConfigManager; +import engine.server.MBServerStatics; + + +public class MiscUtils { + + public static boolean checkIfFirstNameInvalid(String firstName) { + if ((firstName == null) || (firstName.length() == 0) + || (firstName.length() > MBServerStatics.MAX_NAME_LENGTH) + || (firstName.length() < MBServerStatics.MIN_NAME_LENGTH)) { + return true; + } + return (!ConfigManager.regex.get(ConfigManager.MB_LOGIN_FNAME_REGEX).matcher(firstName).matches()); + } + + public static boolean checkIfLastNameInvalid(String lastName) { + if ((lastName != null) && (lastName.length() != 0)) { + // make sure it's less than max length + return lastName.length() > MBServerStatics.MAX_NAME_LENGTH; + // first character: A-Z, a-z + // remaining chars (optional): hyphen, apostrophe, A-Z, a-z, space +// return (!lastNameRegex.matcher(lastName).matches()); + } + // empty last names are fine, return false + return false; + } +} diff --git a/src/engine/util/StringUtils.java b/src/engine/util/StringUtils.java new file mode 100644 index 00000000..d0fcc7a3 --- /dev/null +++ b/src/engine/util/StringUtils.java @@ -0,0 +1,157 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.util; + +import java.net.InetAddress; +import java.util.StringTokenizer; + +public class StringUtils { + + public static String addWS(String s, int totalLen) { + if (s.length() >= totalLen) { + return s; + } + + int diff = totalLen - s.length(); + + String out = s; + + for (int i = 0; i < diff; ++i) { + out += " "; + } + return out; + } + + public static String bannerize(String s, int totalLen) { + if (s.length() >= totalLen) { + return s; + } + + int diff = totalLen - s.length(); + int halfDiff = diff / 2; + + String side = ""; + for (int i = 0; i < halfDiff; ++i) { + side += "*"; + } + + return side + ' ' + s + ' ' + side; + } + + public static String InetAddressToClientString(InetAddress address) { + return address.toString().replaceAll("/", ""); + } + + public static String toHexString(int i) { + return Integer.toHexString(i).toUpperCase(); + } + + public static String toHexString(long l) { + return Long.toHexString(l).toUpperCase(); + } + + // Well done IDA Pro. + + public static int hashString(String toHash) { + byte[] hashArray = toHash.getBytes(); + int hash = 0; + int shift = 0; + if (hashArray.length == 8 || hashArray.length == 7) { + int ecx = 0; + if (hashArray.length == 8) { + ecx = hashArray[7]; + } + int eax = hashArray[4]; + int esi = ecx * 0x8; + eax ^= esi; + ecx ^= 0x5A0; + esi = hashArray[5]; + eax <<= 4; + eax ^= esi; + esi = hashArray[6]; + eax <<= 4; + eax ^= esi; + esi = hashArray[2]; + eax <<= 5; + eax ^= esi; + esi = hashArray[1]; + int edx = hashArray[0]; + eax <<= 5; + eax ^= esi; + ecx /= 2; + ecx /= 2; + eax <<= 5; + ecx ^= edx; + eax ^= ecx; + return eax; + } else { + + for (int i = 0; i < hashArray.length; i++) { + if (i == 0) + shift = 0; + else + shift += 5; + int toShift = hashArray[i] - 0x20; + int shifted = (toShift << shift); + hash ^= shifted; + if (shift > 24) { + int newShift = 0x20 - shift; + int newShifted = toShift >> newShift; + hash ^= newShifted; + if (shift > 27) { + shift -= 0x20; + } + } + } + return hash; + } + } + + public static String wordWrap(String text, int LineWidth) { + StringTokenizer st = new StringTokenizer(text); + int SpaceLeft = LineWidth; + int SpaceWidth = 80; + String outString = ""; + + while (st.hasMoreTokens()) { + String word = st.nextToken(); + if ((word.length() + SpaceWidth) > SpaceLeft) { + outString += '\n' + word + ' '; + SpaceLeft = LineWidth - word.length(); + } else { + outString += word + ' '; + SpaceLeft -= (word.length() + SpaceWidth); + } + + } + + return outString; + + } + + public static String truncate(String input, int length) { + if (input != null && input.length() > length) + input = input.substring(0, length); + return input; + } + + public static int wordCount(String input) { + + String workString = input.trim(); + + if (workString.isEmpty()) + return 0; + + return workString.split("\\s+").length; + + } + +} + diff --git a/src/engine/util/ThreadUtils.java b/src/engine/util/ThreadUtils.java new file mode 100644 index 00000000..e25095f4 --- /dev/null +++ b/src/engine/util/ThreadUtils.java @@ -0,0 +1,39 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.util; + +import org.pmw.tinylog.Logger; + + +public abstract class ThreadUtils { + + private ThreadUtils() { + } + + /** + * Force the current thread to sleep for sec seconds and ms + * milliseconds. + */ + public static void sleep(int sec, long ms) { + try { + Thread.sleep((1000L * sec) + ms); + } catch (InterruptedException e) { + Logger.error(e.toString()); + } + } + + /** + * Force the current thread to sleep for ms milliseconds. + */ + public static void sleep(long ms) { + ThreadUtils.sleep(0, ms); + } + +} diff --git a/src/engine/workthreads/DestroyCityThread.java b/src/engine/workthreads/DestroyCityThread.java new file mode 100644 index 00000000..aeb46a6c --- /dev/null +++ b/src/engine/workthreads/DestroyCityThread.java @@ -0,0 +1,155 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.workthreads; + +/* + * This thread is spawned to process destruction + * of a player owned city, including subguild + * and database cleanup. + * + * The 'destroyed' city zone persists until the + * next reboot. + */ + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3fImmutable; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.Guild; +import engine.objects.Zone; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +public class DestroyCityThread implements Runnable { + + City city; + + public DestroyCityThread(City city) { + + this.city = city; + } + + public void run() { + + // Member variable declaration + + Zone cityZone; + Zone newParent; + Guild formerGuild; + Vector3fImmutable localCoords; + ArrayList subGuildList; + + // Member variable assignment + + cityZone = city.getParent(); + newParent = cityZone.getParent(); + formerGuild = city.getTOL().getGuild(); + + // Former guild loses it's tree! + + if (DbManager.GuildQueries.SET_GUILD_OWNED_CITY(formerGuild.getObjectUUID(), 0)) { + + //Successful Update of guild + + formerGuild.setGuildState(engine.Enum.GuildState.Errant); + formerGuild.setNation(null); + formerGuild.setCityUUID(0); + GuildManager.updateAllGuildTags(formerGuild); + GuildManager.updateAllGuildBinds(formerGuild, null); + } + + // By losing the tree, the former owners lose all of their subguilds. + + if (formerGuild.getSubGuildList().isEmpty() == false) { + + subGuildList = new ArrayList<>(); + + for (Guild subGuild : formerGuild.getSubGuildList()) { + subGuildList.add(subGuild); + } + + for (Guild subGuild : subGuildList) { + formerGuild.removeSubGuild(subGuild); + } + } + + // Build list of buildings within this parent zone + + for (Building cityBuilding : cityZone.zoneBuildingSet) { + + // Sanity Check in case player deletes the building + // before this thread can get to it + + if (cityBuilding == null) + continue; + + // Do nothing with the banestone. It will be removed elsewhere + + if (cityBuilding.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.BANESTONE)) + continue; + + // All buildings are moved to a location relative + // to their new parent zone + + localCoords = ZoneManager.worldToLocal(cityBuilding.getLoc(), newParent); + + DbManager.BuildingQueries.MOVE_BUILDING(cityBuilding.getObjectUUID(), newParent.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z); + + // All buildings are re-parented to a zone one node + // higher in the tree (continent) as we will be + // deleting the city zone very shortly. + + if (cityBuilding.getParentZoneID() != newParent.getParentZoneID()) + cityBuilding.setParentZone(newParent); + + // No longer a tree, no longer any protection contract! + + cityBuilding.setProtectionState(Enum.ProtectionState.NONE); + + // Destroy all remaining city assets + + if ((cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BARRACK) + || (cityBuilding.getBlueprint().isWallPiece()) + || (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SHRINE) + || (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.TOL) + || (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SPIRE) + || (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.WAREHOUSE)) { + + if (cityBuilding.getRank() != -1) + cityBuilding.setRank(-1); + } + } + + if (city.getRealm() != null) + city.getRealm().removeCity(city.getObjectUUID()); + + // It's now safe to delete the city zone from the database + // which will cause a cascade delete of everything else + + + if (DbManager.ZoneQueries.DELETE_ZONE(cityZone) == false) { + Logger.error("DestroyCityThread", "Database error when deleting city zone: " + cityZone.getObjectUUID()); + return; + } + + // Refresh the city for map requests + + City.lastCityUpdate = System.currentTimeMillis(); + + // Zone and city should vanish upon next reboot + // if the codebase reaches here. + + Logger.info(city.getParent().getName() + " uuid:" + city.getObjectUUID() + "has been destroyed!"); + } +} diff --git a/src/engine/workthreads/DisconnectTrashTask.java b/src/engine/workthreads/DisconnectTrashTask.java new file mode 100644 index 00000000..b0cf6748 --- /dev/null +++ b/src/engine/workthreads/DisconnectTrashTask.java @@ -0,0 +1,53 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.workthreads; + +import engine.Enum; +import engine.gameManager.DbManager; +import engine.gameManager.SessionManager; +import engine.objects.PlayerCharacter; +import engine.session.Session; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.TimerTask; + +public class DisconnectTrashTask extends TimerTask { + + private final ArrayList trashList; + + // Pass it a list of characters and it will disconnect them + // 5 seconds in the future. + + public DisconnectTrashTask(ArrayList trashList) { + this.trashList = new ArrayList<>(trashList); + } + + public void run() { + + Logger.info("Disconnecting actives from pool of: " + trashList.size()); + + Session trashSession; + int accountUID; + + for (PlayerCharacter trashPlayer : trashList) { + trashSession = SessionManager.getSession(trashPlayer); + accountUID = trashPlayer.getAccount().getObjectUUID(); + + if (trashSession != null) + trashSession.getConn().disconnect(); + + // Remove account from cache + + DbManager.removeFromCache(Enum.GameObjectType.Account, accountUID); + } + } + + ; +} diff --git a/src/engine/workthreads/HourlyJobThread.java b/src/engine/workthreads/HourlyJobThread.java new file mode 100644 index 00000000..dbf17e0c --- /dev/null +++ b/src/engine/workthreads/HourlyJobThread.java @@ -0,0 +1,289 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.workthreads; + +import engine.Enum; +import engine.InterestManagement.WorldGrid; +import engine.db.archive.DataWarehouse; +import engine.db.archive.MineRecord; +import engine.gameManager.*; +import engine.net.DispatchMessage; +import engine.net.MessageDispatcher; +import engine.net.client.msg.chat.ChatSystemMsg; +import engine.objects.*; +import engine.server.world.WorldServer; +import org.pmw.tinylog.Logger; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +import static engine.server.MBServerStatics.MINE_LATE_WINDOW; + +public class HourlyJobThread implements Runnable { + + public HourlyJobThread() { + + } + + public static void decayShrines() { + ArrayList shrineList = new ArrayList<>(); + + for (Shrine shrine : Shrine.shrinesByBuildingUUID.values()) { + try { + Building shrineBuilding = (Building) DbManager.getObject(Enum.GameObjectType.Building, shrine.getBuildingID()); + + if (shrineBuilding == null) + continue; + + + if (shrineBuilding.getOwner().equals(shrineBuilding.getCity().getOwner()) == false) + shrineBuilding.claim(shrineBuilding.getCity().getOwner()); + } catch (Exception e) { + Logger.info("Shrine " + shrine.getBuildingID() + " Error " + e); + } + } + + // Grab list of top two shrines of each type + + for (Shrine shrine : Shrine.shrinesByBuildingUUID.values()) { + + if (shrine.getRank() == 0 || shrine.getRank() == 1) + shrineList.add(shrine); + } + + Logger.info("Decaying " + shrineList.size() + " shrines..."); + + // Top 2 shrines decay by 10% a day + + for (Shrine shrine : shrineList) { + + try { + shrine.decay(); + } catch (Exception e) { + Logger.info("Shrine " + shrine.getBuildingID() + " Error " + e); + } + } + } + + public static void processMineWindow() { + + try { + + ArrayList mines = Mine.getMines(); + + for (Mine mine : mines) { + if (LocalDateTime.now().getHour() == 1400) { + mine.wasClaimed = false; + } + try { + + // Open Errant Mines + + if (mine.getOwningGuild().isEmptyGuild()) { + HourlyJobThread.mineWindowOpen(mine); + continue; + } + + // Open Mines owned by nations having their WOO + // set to the current mine window. + + if (mine.getOwningGuild().getNation().getMineTime() == + LocalDateTime.now().getHour() && mine.wasClaimed == false) { + HourlyJobThread.mineWindowOpen(mine); + continue; + } + + // Close the mine if it reaches this far + + mineWindowClose(mine); + + } catch (Exception e) { + Logger.error("mineID: " + mine.getObjectUUID(), e.toString()); + } + } + } catch (Exception e) { + Logger.error(e.toString()); + } + } + + public static void mineWindowOpen(Mine mine) { + + mine.setActive(true); + ChatManager.chatSystemChannel(mine.getZoneName() + "'s Mine is now Active!"); + Logger.info(mine.getZoneName() + "'s Mine is now Active!"); + } + + public static boolean mineWindowClose(Mine mine) { + + // No need to end the window of a mine which never opened. + + if (mine.isActive == false) + return false; + + Building mineBuilding = BuildingManager.getBuildingFromCache(mine.getBuildingID()); + + if (mineBuilding == null) { + Logger.debug("Null mine building for Mine " + mine.getObjectUUID() + " Building " + mine.getBuildingID()); + return false; + } + + // Mine building still stands; nothing to do. + // We can early exit here. + + if (mineBuilding.getRank() > 0) { + mine.setActive(false); + mine.lastClaimer = null; + return true; + } + + // This mine does not have a valid claimer + // we will therefore set it to errant + // and keep the window open. + + if (!Mine.validateClaimer(mine.lastClaimer)) { + mine.lastClaimer = null; + mine.updateGuildOwner(null); + mine.setActive(true); + return false; + } + + //Update ownership to map + + mine.guildName = mine.getOwningGuild().getName(); + mine.guildTag = mine.getOwningGuild().getGuildTag(); + Guild nation = mine.getOwningGuild().getNation(); + mine.nationName = nation.getName(); + mine.nationTag = nation.getGuildTag(); + + mineBuilding.rebuildMine(); + WorldGrid.updateObject(mineBuilding); + + ChatSystemMsg chatMsg = new ChatSystemMsg(null, mine.lastClaimer.getName() + " has claimed the mine in " + mine.getParentZone().getParent().getName() + " for " + mine.getOwningGuild().getName() + ". The mine is no longer active."); + chatMsg.setMessageType(10); + chatMsg.setChannel(Enum.ChatChannelType.SYSTEM.getChannelID()); + DispatchMessage.dispatchMsgToAll(chatMsg); + + // Warehouse this claim event + + MineRecord mineRecord = MineRecord.borrow(mine, mine.lastClaimer, Enum.RecordEventType.CAPTURE); + DataWarehouse.pushToWarehouse(mineRecord); + + mineBuilding.setRank(mineBuilding.getRank()); + mine.lastClaimer = null; + mine.setActive(false); + mine.wasClaimed = true; + return true; + } + + public void run() { + + // *** REFACTOR: TRY TRY TRY TRY {{{{{{{{{{{ OMG + + Logger.info("Hourly job is now running."); + + try { + + // Use the same hotZone this hour up and until + // the HotZone_Duration from the ConfigManager + + if (ZoneManager.hotZone == null) + ZoneManager.generateAndSetRandomHotzone(); + else + ZoneManager.hotZoneCycle = ZoneManager.hotZoneCycle + 1; + + if (ZoneManager.hotZoneCycle > Integer.parseInt(ConfigManager.MB_HOTZONE_DURATION.getValue())) + ZoneManager.generateAndSetRandomHotzone(); + + if (ZoneManager.hotZone == null) { + Logger.error("Null HotZone returned from ZoneManager"); + } else { + Logger.info("HotZone switched to: " + ZoneManager.hotZone.getName()); + } + + } catch (Exception e) { + Logger.error(e.toString()); + } + + // Open or Close mines for the current mine window. + + processMineWindow(); + + // Deposit mine resources to Guilds + + for (Mine mine : Mine.getMines()) { + + try { + mine.depositMineResources(); + } catch (Exception e) { + Logger.info(e.getMessage() + " for Mine " + mine.getObjectUUID()); + } + } + + // Reset time-gated access to WOO slider. + // *** Do this after the mines open/close! + + if (LocalDateTime.now().getHour() == MINE_LATE_WINDOW) { + Guild guild; + + for (AbstractGameObject dbObject : DbManager.getList(Enum.GameObjectType.Guild)) { + guild = (Guild) dbObject; + + if (guild != null) + guild.wooWasModified = false; + } + } + + // Mines can only be claimed once per cycle. + // This will reset at 1am after the last mine + // window closes. + + if (LocalDateTime.now().getHour() == MINE_LATE_WINDOW + 1) { + + for (Mine mine : Mine.getMines()) { + + if (mine.wasClaimed == true) + mine.wasClaimed = false; + } + } + + // Decay Shrines at midnight every day + + if (LocalDateTime.now().getHour() == MINE_LATE_WINDOW) + decayShrines(); + + // Update city population values + + ConcurrentHashMap map = DbManager.getMap(Enum.GameObjectType.City); + + if (map != null) { + + for (AbstractGameObject ago : map.values()) { + + City city = (City) ago; + + if (city != null) + if (city.getGuild() != null) { + ArrayList guildList = Guild.GuildRoster(city.getGuild()); + city.setPopulation(guildList.size()); + } + } + City.lastCityUpdate = System.currentTimeMillis(); + } else { + Logger.error("missing city map"); + } + + // Log metrics to console + Logger.info(WorldServer.getUptimeString()); + Logger.info(SimulationManager.getPopulationString()); + Logger.info(MessageDispatcher.getNetstatString()); + Logger.info(PurgeOprhans.recordsDeleted.toString() + "orphaned items deleted"); + } +} diff --git a/src/engine/workthreads/PurgeOprhans.java b/src/engine/workthreads/PurgeOprhans.java new file mode 100644 index 00000000..6287d67f --- /dev/null +++ b/src/engine/workthreads/PurgeOprhans.java @@ -0,0 +1,65 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.workthreads; + +import engine.gameManager.DbManager; +import org.pmw.tinylog.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.concurrent.atomic.LongAdder; + +/* + * This thread runs at bootstrap to ensure cleanup of + * orphaned items (deleted items). God does this mess + * ever need to be refactored and re-use of item uuid's + * implemented. + */ +public class PurgeOprhans implements Runnable { + + public static LongAdder recordsDeleted = new LongAdder(); + + public PurgeOprhans() { + + recordsDeleted.reset(); + + } + + public static void startPurgeThread() { + + Thread purgeOrphans; + purgeOrphans = new Thread(new PurgeOprhans()); + + purgeOrphans.setName("purgeOrphans"); + purgeOrphans.start(); + } + + public void run() { + + // Member variable declaration + + try ( + Connection connection = DbManager.getConnection(); + PreparedStatement statement = connection.prepareStatement("SELECT * from `object` where `type` = 'item' AND `parent` IS NULL", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + rs.deleteRow(); + recordsDeleted.increment(); + } + + } catch (Exception e) { + Logger.error(e.toString()); + } + + Logger.info("Thread is exiting with " + recordsDeleted.toString() + " items deleted"); + } + +} diff --git a/src/engine/workthreads/TransferCityThread.java b/src/engine/workthreads/TransferCityThread.java new file mode 100644 index 00000000..812f82fe --- /dev/null +++ b/src/engine/workthreads/TransferCityThread.java @@ -0,0 +1,99 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + +package engine.workthreads; + +/* + * This thread is spawned to process transfer + * ownership of a player owned city, including + * subguild and database cleanup. + * + */ + +import engine.Enum; +import engine.gameManager.BuildingManager; +import engine.gameManager.DbManager; +import engine.gameManager.GuildManager; +import engine.net.DispatchMessage; +import engine.net.client.msg.CityZoneMsg; +import engine.objects.AbstractCharacter; +import engine.objects.City; +import engine.objects.Guild; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; + +public class TransferCityThread implements Runnable { + + City city; + AbstractCharacter newOwner; + + public TransferCityThread(City city, AbstractCharacter newOwner) { + + this.city = city; + this.newOwner = newOwner; + } + + public void run() { + + Guild formerGuild; + ArrayList subGuildList; + + formerGuild = this.city.getTOL().getGuild(); + + // Former guild loses it's tree! + + if (formerGuild != null) + if (DbManager.GuildQueries.SET_GUILD_OWNED_CITY(formerGuild.getObjectUUID(), 0)) { + formerGuild.setGuildState(Enum.GuildState.Errant); + formerGuild.setNation(null); + formerGuild.setCityUUID(0); + GuildManager.updateAllGuildTags(formerGuild); + GuildManager.updateAllGuildBinds(formerGuild, null); + } + + // By losing the tree, the former owners lose all of their subguilds. + + if (formerGuild.getSubGuildList().isEmpty() == false) { + + subGuildList = new ArrayList<>(); + + for (Guild subGuild : formerGuild.getSubGuildList()) { + subGuildList.add(subGuild); + } + + for (Guild subGuild : subGuildList) { + formerGuild.removeSubGuild(subGuild); + } + } + + //Reset TOL to rank 1 + + city.getTOL().setRank(1); + + // Transfer all assets to new owner + + city.claim(newOwner); + + //Set name of City to attacker's guild name + BuildingManager.setUpgradeDateTime(city.getTOL(), null, 0); + city.getTOL().setName(newOwner.getGuild().getName()); + + // Send updated cityZone to players + CityZoneMsg czm = new CityZoneMsg(2, city.getTOL().getLoc().x, city.getTOL().getLoc().y, city.getTOL().getLoc().z, city.getTOL().getName(), city.getTOL().getParentZone(), 0f, 0f); + + DispatchMessage.dispatchMsgToAll(czm); + + // Reset city timer for map update + + City.lastCityUpdate = System.currentTimeMillis(); + + Logger.info("uuid:" + city.getObjectUUID() + "transferred from " + formerGuild.getName() + + " to " + newOwner.getGuild().getName()); + } +} diff --git a/src/engine/workthreads/WarehousePushThread.java b/src/engine/workthreads/WarehousePushThread.java new file mode 100644 index 00000000..77c368f5 --- /dev/null +++ b/src/engine/workthreads/WarehousePushThread.java @@ -0,0 +1,448 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2022 +// www.magicbane.com + + +package engine.workthreads; + +/* + * This thread pushes cumulative warehouse data to + * a remote database. + * + */ + +import engine.Enum; +import engine.db.archive.*; +import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import org.pmw.tinylog.Logger; + +import java.sql.*; + +public class WarehousePushThread implements Runnable { + + // Used to track last push. These are read + // at thread startup and written back out + // when we're done + + public static int charIndex, charDelta; + public static int cityIndex, cityDelta; + public static int guildIndex, guildDelta; + public static int realmIndex, realmDelta; + public static int baneIndex, baneDelta; + public static int pvpIndex, pvpDelta; + public static int mineIndex, mineDelta; + + public WarehousePushThread() { + + } + + public static boolean pushMineRecords() { + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = MineRecord.buildMineQueryStatement(localConnection); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + pushMineRecord(rs); + mineDelta = rs.getInt("event_number"); + } + + return true; + } catch (SQLException e) { + Logger.error("Error with local DB connection: " + e.toString()); + e.printStackTrace(); + return false; + } + } + + public static boolean pushCharacterRecords() { + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = CharacterRecord.buildCharacterQueryStatement(localConnection); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + pushCharacterRecord(rs); + charDelta = rs.getInt("event_number"); + } + + return true; + } catch (SQLException e) { + Logger.error("Error with local DB connection: " + e.toString()); + e.printStackTrace(); + return false; + } + } + + private static boolean pushGuildRecords() { + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = GuildRecord.buildGuildQueryStatement(localConnection); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + pushGuildRecord(rs); + guildDelta = rs.getInt("event_number"); + } + + return true; + } catch (SQLException e) { + Logger.error("Error with local DB connection: " + e.toString()); + e.printStackTrace(); + return false; + } + } + + private static boolean pushMineRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = MineRecord.buildMinePushStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + } + + private static boolean pushGuildRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = GuildRecord.buildGuildPushStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + } + + private static boolean pushBaneRecords() { + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = BaneRecord.buildBaneQueryStatement(localConnection); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + pushBaneRecord(rs); + baneDelta = rs.getInt("event_number"); + } + + return true; + } catch (SQLException e) { + Logger.error("Error with local DB connection: " + e.toString()); + e.printStackTrace(); + return false; + } + } + + private static boolean pushBaneRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = BaneRecord.buildBanePushStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + } + + private static boolean pushCityRecords() { + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = CityRecord.buildCityQueryStatement(localConnection); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + pushCityRecord(rs); + cityDelta = rs.getInt("event_number"); + } + + return true; + } catch (SQLException e) { + Logger.error("Error with local DB connection: " + e.toString()); + e.printStackTrace(); + return false; + } + } + + private static boolean pushPvpRecords() { + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = PvpRecord.buildPvpQueryStatement(localConnection); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + if (pushPvpRecord(rs) == true) + pvpDelta = rs.getInt("event_number"); + } + + return true; + } catch (SQLException e) { + Logger.error("Error with local DB connection: " + e.toString()); + return false; + } + } + + private static boolean pushPvpRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = PvpRecord.buildPvpPushStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + + } + + private static boolean pushRealmRecords() { + + try (Connection localConnection = DbManager.getConnection(); + PreparedStatement statement = RealmRecord.buildRealmQueryStatement(localConnection); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + + if (pushRealmRecord(rs) == true) + realmDelta = rs.getInt("event_number"); + } + + return true; + } catch (SQLException e) { + Logger.error("Error with local DB connection: " + e.toString()); + e.printStackTrace(); + return false; + } + } + + private static boolean pushRealmRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = RealmRecord.buildRealmPushStatement(remoteConnection, rs)) { + statement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + + } + + private static boolean pushCharacterRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = CharacterRecord.buildCharacterPushStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + + } + + private static boolean pushCityRecord(ResultSet rs) { + + try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection(); + PreparedStatement statement = CityRecord.buildCityPushStatement(remoteConnection, rs)) { + + statement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + } + + private static boolean readWarehouseIndex() { + + // Member variable declaration + + String queryString; + + queryString = "SELECT * FROM `warehouse_index`"; + + try (Connection localConnection = DbManager.getConnection(); + CallableStatement statement = localConnection.prepareCall(queryString); + ResultSet rs = statement.executeQuery()) { + + while (rs.next()) { + charIndex = rs.getInt("charIndex"); + cityIndex = rs.getInt("cityIndex"); + guildIndex = rs.getInt("guildIndex"); + realmIndex = rs.getInt("realmIndex"); + baneIndex = rs.getInt("baneIndex"); + pvpIndex = rs.getInt("pvpIndex"); + mineIndex = rs.getInt("mineIndex"); + } + + return true; + + } catch (SQLException e) { + Logger.error("Error reading warehouse index" + e.toString()); + e.printStackTrace(); + return false; + } + } + + private static boolean updateWarehouseIndex() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement statement = WarehousePushThread.buildIndexUpdateStatement(connection)) { + + statement.execute(); + return true; + + } catch (SQLException e) { + Logger.error(e.toString()); + return false; + } + } + + private static PreparedStatement buildIndexUpdateStatement(Connection connection) throws SQLException { + + PreparedStatement outStatement = null; + String queryString = "UPDATE `warehouse_index` SET `charIndex` = ?, `cityIndex` = ?, `guildIndex` = ?, `realmIndex` = ?, `baneIndex` = ?, `pvpIndex` = ?, `mineIndex` = ?"; + outStatement = connection.prepareStatement(queryString); + + // Bind record data + + outStatement.setInt(1, charIndex); + outStatement.setInt(2, cityIndex); + outStatement.setInt(3, guildIndex); + outStatement.setInt(4, realmIndex); + outStatement.setInt(5, baneIndex); + outStatement.setInt(6, pvpIndex); + outStatement.setInt(7, mineIndex); + return outStatement; + } + + public void run() { + + int recordCount = 0; + boolean writeSuccess = true; + + if (ConfigManager.MB_WORLD_WAREHOUSE_PUSH.getValue().equals("false")) { + Logger.info("WAREHOUSEPUSH DISABLED: EARLY EXIT"); + return; + } + + // Cache where we left off from the last push + // for each of the warehouse tables + + if (readWarehouseIndex() == false) + return; + + // Log run to console + + Logger.info("Pushing records to remote..."); + + // Push records to remote database + + for (Enum.DataRecordType recordType : Enum.DataRecordType.values()) { + + switch (recordType) { + case PVP: + if (pushPvpRecords() == true) { + recordCount = Math.max(0, pvpDelta - pvpIndex); + pvpIndex += recordCount; + } else + writeSuccess = false; + break; + case CHARACTER: + if (pushCharacterRecords() == true) { + recordCount = Math.max(0, charDelta - charIndex); + charIndex += recordCount; + } else + writeSuccess = false; + break; + case REALM: + if (pushRealmRecords() == true) { + recordCount = Math.max(0, realmDelta - realmIndex); + realmIndex += recordCount; + } else + writeSuccess = false; + break; + case GUILD: + if (pushGuildRecords() == true) { + recordCount = Math.max(0, guildDelta - guildIndex); + guildIndex += recordCount; + } else + writeSuccess = false; + break; + case BANE: + if (pushBaneRecords() == true) { + recordCount = Math.max(0, baneDelta - baneIndex); + baneIndex += recordCount; + } else + writeSuccess = false; + break; + case CITY: + if (pushCityRecords() == true) { + recordCount = Math.max(0, cityDelta - cityIndex); + cityIndex += recordCount; + } else + writeSuccess = false; + break; + case MINE: + if (pushMineRecords() == true) { + recordCount = Math.max(0, mineDelta - mineIndex); + mineIndex += recordCount; + } else + writeSuccess = false; + break; + default: + recordCount = 0; + writeSuccess = false; + break; // unhandled type + } + + if (writeSuccess == true) + Logger.info(recordCount + " " + recordType.name() + " records sent to remote"); + else + Logger.info(recordCount + " returning failed success"); + + } // Iterate switch + + // Update indices + + updateWarehouseIndex(); + + // Update dirty records + + Logger.info("Pushing updates of dirty warehouse records"); + CharacterRecord.updateDirtyRecords(); + + if (charDelta > 0) + Logger.info(charDelta + " dirty character records were sent"); + ; + BaneRecord.updateDirtyRecords(); + + if (baneDelta > 0) + Logger.info(baneDelta + " dirty bane records were sent"); + + Logger.info("Process has completed"); + + } +} +